Trong chương trình RotPosPathApp, hoạt ảnh xuất hiện không tự
nhiên chủ yếu là do sự kết hợp vị trí các điểm mút được chọn. Khi khối
hộp ColorCube di chuyển đến mỗi vị trí điểm mút xác định, thì ngay lập
tức chuyển động của nó thay đổi để đến được điểm mút tiếp theo. Di
chuyển như vậy không tự nhiên do trong thế giới thực, chuyển động của
tất cả các vật đều có tính ì nhất định.
571 trang |
Chia sẻ: lvcdongnoi | Lượt xem: 4012 | Lượt tải: 2
Bạn đang xem trước 20 trang tài liệu Lập trình đồ họa với Java 3D, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
);
Vector3f translate = new Vector3f();
525
Lập trình đồ họa trên Java 2D và 3D
Transform3D T3D = new Transform3D();
TransformGroup TGT = null;
TransformGroup TGR = null;
Billboard billboard = null;
BoundingSphere bSphere = new
BoundingSphere();
objRoot.addChild(createLand());
SharedGroup share = new SharedGroup();
share.addChild(createTree());
float[][] position = { { 0.0f, 0.0f,
-3.0f }, { 6.0f, 0.0f, 0.0f },
{ 6.0f, 0.0f, 6.0f }, { 3.0f,
0.0f, -10.0f },
{ 13.0f, 0.0f, -30.0f }, {
-13.0f, 0.0f, 30.0f },
{ -13.0f, 0.0f, 23.0f },
{ 13.0f, 0.0f, 3.0f } };
for (int i = 0; i < position.length; i++)
{
translate.set(position[i]);
T3D.setTranslation(translate);
TGT = new TransformGroup(T3D);
TGR = new TransformGroup();
526
Lập trình đồ họa trên Java 2D và 3D
TGR.setCapability(TransformGroup.ALLOW_TRANSFORM_
WRITE);
billboard = new Billboard(TGR);
billboard.setSchedulingBounds(bSphere);
//
billboard.setAlignmentMode(Billboard.ROTATE_ABOUT
_POINT);
objRoot.addChild(TGT);
objRoot.addChild(billboard);
TGT.addChild(TGR);
TGR.addChild(new Link(share));
}
vpTrans =
su.getViewingPlatform().getViewPlatformTransform(
);
translate.set(0.0f, 0.3f, 0.0f);
T3D.setTranslation(translate);
vpTrans.setTransform(T3D);
KeyNavigatorBehavior keyNavBeh = new
KeyNavigatorBehavior(vpTrans);
keyNavBeh
.setSchedulingBounds(new
BoundingSphere(new Point3d(), 1000.0));
objRoot.addChild(keyNavBeh);
Background background = new Background();
background.setColor(0.3f, 0.3f, 1.0f);
527
Lập trình đồ họa trên Java 2D và 3D
background.setApplicationBounds(new
BoundingSphere());
objRoot.addChild(background);
// Let Java 3D perform optimizations on
this scene graph.
objRoot.compile();
return objRoot;
} // end of CreateSceneGraph method of
BillboardAppĐoạn mã trên được trích từ phương thức
createSenceGraph của chương trình BillboardApp.java với các kí hiệu
đánh dấu tương ứng với các bước thực hiện được liệt kê trong hình 5-15.
Hình 5-16. Biểu đồ đồ thị sử dụng đối tượng Billboard trong
BillboardApp.java
Hình 5-17 hiển thị một cảnh sinh bởi chương trình ví dụ BillboardApp.
528
Lập trình đồ họa trên Java 2D và 3D
Hình 5-17. Một ảnh của BillboardApp với tất cả “cây” 2 chiều đều
hướng về phía người quan sát BillboardApp.java cung cấp
KeyNavigatorBehavior cho phép người dùng di chuyển xung quanh và
quan sát các cây từ các vị trí và hướng nhìn khác nhau. Xin xem lại phần
4.4.2 của chương 4 để biết thêm chi tiết về lớp đối tượng này.
Giao diện lập trình ứng dụng của Billboard (Billboard
API)
Trong ví dụ BillboardApp phía trên, đối tượng Billboard hoạt động
với chế độ mặc định là quay đối tượng trực quan xung quanh trục y. Nên,
nếu các cây trong ví dụ này được quan sát từ phía trên hay từ phía dưới,
dạng 2 chiều của nó sẽ bị lộ.
Một chế độ thay thế là quay đối tượng trực quan quanh một điểm.
Trong chế độ này, hình ảnh 2 chiều được điều khiển quay quanh 1 điểm,
do đó, đối tượng trực quan luôn được quan sát trực diện từ bất cứ vị trí
quan sát nào. Một ứng dụng dễ thấy là biểu diễn mặt trăng hay các vật thể
hình cầu ở khoảng cách xa dưới dạng một hình tròn.
529
Lập trình đồ họa trên Java 2D và 3D
Các phương thức khởi tạo của Billboard
Billboard ()
Khởi tạo một Billboard với tham số mặc định: mode =
ROTATE_ABOUT_AXIS, axis = (0, 1, 0).
Billboard (TransformGroup tg)
Khởi tạo một đối tượng Billboard với các tham số mặc định hoạt
động trên đối tượng TransformGroup xác định.
Billboard (TransformGroup tg, int mode, Vector3f axis)
Khởi tạo đối tượng Billboard với trục quay và chế độ hoạt động xác
định điều khiển đối tượng TransformGroup xác định.
Billboard (TransformGroup tg, int mode, Point3f point)
Khởi tạo đối tượng Billboard với tâm quay và chế độ xác định, hoạt
động trên đối tượng đích TransformGroup xác định.
Một vài phương thức khác cung cấp bởi lớp Billboard :
void setAlignmentAxis(Vector3f axis)
Thiết lập trục canh chỉnh.
void setAlignmentAxis(float x, float y, float z)
Thiết lập trục canh chỉnh.
void setAlignmentMode(int mode)
Thiết lập chế độ canh chỉnh, trong đó mode có thể là
ROTATE_ABOUT_AXIS - Chỉ định quay xung quanh một
trục xác định
ROTATE_ABOUT_POINT - Chỉ định quay xung quanh một điểm
xác định và chỉ định rằng trục Y của đối tượng con phải phù hợp với trục
Y của đối tượng được quan sát
void setRotationPoint(Point3f point)
Thiết lập tâm quay
void setRotationPoint(float x, float y, float z)
Thiết lập tâm quay
530
Lập trình đồ họa trên Java 2D và 3D
void setTarget(TransformGroup tg)
Thiết lập đối tượng đích TransformGroup cho đối tượng Billboard
hiện thời.
OrientedShape3D
Đối tượng OrientedShape3D được sử dụng để thực hiện các chức
năng giống như đối tượng Billboard trong mục trước. Điểm khác biệt
chủ yếu giữa hai lớp đối tượng như đã được nhắc đến ở phần trên là ở chỗ
OrientedShape3D có khả năng làm việc với nhiều hơn một quan sát còn
Billboard thì không, cũng như khả năng chia sẻ sử dụng chỉ có ở
OrientedShape3D.
Đối tượng OrientedShape3D cũng yêu cầu sử dụng ít mã hơn.
OrientedShape3D không hiệu chỉnh đối tượng đích TransformGroup, mà
để mở khả năng này cho mã chương trình thực hiện. OrientedShape3D
không phải là một đối tượng hành vi do đó, người lập trình cũng không
cần quan tâm đến giới hạn hoạt động.
Với so sánh trên thì dễ thấy OrientedShape3D là sự lựa chọn hiển
nhiên cho các ứng dụng dùng hiệu ứng Billboard . Nguyên nhân duy nhất
để không loại bỏ lớp Billboard khỏi Java3D API chính là khả năng
tương thích ngược với các ứng dụng đang tồn tại.
Đối tượng OrientedShape3D quay xung quanh một trục hoặc một tâm.
Trong cả hai trường hợp, đối tượng OrientedShape3D tự định hướng bản
thân nó sao cho trục z+ của các con của nó luôn đối diện với người quan
sát. Do OrientedShape3D hướng trục z+ của các vật thể về phía người
quan sát, nên không có ý nghĩa gì nếu quay vật thể quanh trục z. Vì lí do
này nên trục quay không được song song với trục z, có nghĩa là, trục quay
không được định nghĩa là (0, 0, z) với bất cứ giá trị nào của z. Nếu một
trục song song với z được chỉ định, OrientedShape3D đơn giản sẽ không
531
Lập trình đồ họa trên Java 2D và 3D
làm gì cả. Lúc đó coi như là đối tượng TransformGroup được thiết lập ma
trận đồng nhất.
Không có giới hạn cho giá trị được sử dụng để làm tâm quay. Nếu chế
độ biểu diễn được thiết lập là ROTATE_ABOUT_POINT thì vật thể sẽ
quay xung quanh một điểm xác định.
Giao diện lập trình ứng dụng của OrientedShape3D
Giao diện lập trình ứng dụng của OrientedShape3D là tương tự với
giao diện lập trình của lớp Billboard , ngoài ra, cũng có vài điểm khác
biệt. Sự khác biệt phát sinh chủ yếu do sự khác nhau về cấu trúc phân cấp
lớp đối tượng. OrientedShape3D kế thừa từ lớp Shape3D, trong khi đó,
Billboard lại kế thừa từ Behavior.
Danh sách các phương thức khởi tạo của OrientedShape3D không
nhiều như của lớp Shape. Ví dụ như không có phương thức khởi tạo với
chỉ một tham số Geometry. Trong trường hợp này, các phương thức khởi
tạo không kế thừa lớp cơ sở. Một ứng dụng có thể sử dụng phương thức
khởi tạo không tham số và một vài phương thức khác để tạo ra đối tượng
OrientedShape3D cần thiết.
Các phương thức khởi tạo:
OrientedShape3D()
Khởi tạo đối tượng OrientedShape3D với các tham số mặc định
OrientedShape3D(Geometry geometry, Appearance appearance,
int mode, Point3f point)
Khởi tạo đối tượng OrientedShape3D với thành phần hình học, thành
phần xuất hiện, chế độ và tâm quay xác định.
OrientedShape3D(Geometry geometry, Appearance appearance,
int mode, Vector3f axis)
Khởi tạo đối tượng OrientedShape3D với thành phần hình học, thành
phần xuất hiện, chế độ và trục quay xác định.
532
Lập trình đồ họa trên Java 2D và 3D
Một số phương thức khác cung cấp bởi OrientedShape3D:
void setAlignmentAxis(float x, float y, float z)
void setAlignmentAxis(Vector3f axis)
void setAlignmentMode(int mode)
void setRotationPoint(float x, float y, float z)
void setRotationPoint(Point3f point)
Các phương thức này có các tham số và chức năng giống với các phương
thức cùng tên của Billboard . Xem chi tiết ở phần “Giao diện lập trình
ứng dụng của Billboard ”.
Các khả năng có thể thiết lập cho OrientedShape3D:
ALLOW_AXIS_READ | WRITE
Cho phép đọc (ghi) thông tin trục canh chỉnh.
ALLOW_MODE_READ | WRITE
Cho phép đọc (ghi) thông tin chế độ canh chỉnh.
ALLOW_POINT_READ | WRITE
Cho phép đọc (ghi) thông tin tâm quay.
Ví dụ sử dụng OrientedShape3D
Việc sử dụng OrientedShape3D rất dễ dàng, bất cứ chỗ nào sử dụng
đối tượng Shape3D đều có thể thay thế đơn giản bằng đối tượng
OrientedShape3D.
Xét chương trình ví dụ OrientedShape3DApp.java. Ứng dụng này tạo
một phong cảnh ảo với các cây Billboard 2 chiều. Mỗi cây là một con
của một đối tượng OrientedShape3D, đối tượng này tạo hiệu ứng
Billboard cho cây.
1. public BranchGroup createSceneGraph(SimpleUniverse su) {
2. // Create the root of the branch graph
3. BranchGroup objRoot = new BranchGroup();
533
Lập trình đồ họa trên Java 2D và 3D
4.
5. Vector3f translate = new Vector3f();
6. Transform3D T3D = new Transform3D();
7. TransformGroup positionTG = null;
8. OrientedShape3D orientedShape3D = null;
9.
10. Geometry treeGeom = createTree();
11.
12. //specify the position of the trees
13. float[][] position = {{ 0.0f, 0.0f, -2.0f},
14. {-13.0f, 0.0f, 23.0f},
15. { 1.0f, 0.0f, -3.5f}};
16.
17. // for the positions in the array create a OS3D
18. for (int i = 0; i < position.length; i++){
19. translate.set(position[i]);
20. T3D.setTranslation(translate);
21. positionTG = new TransformGroup(T3D);
22.
23. orientedShape3D = new OrientedShape3D();
24. orientedShape3D.addGeometry(treeGeom);
25.
26. objRoot.addChild(positionTG);
27. positionTG.addChild(orientedShape3D);
28. }
Đoạn mã trên được trích từ phương thức createSceneGraph của
chương trình OrientedShape3DApp.java. Có thể nhận thấy tính dễ dùng
của OrientedShape3D so với Billboard .
534
Lập trình đồ họa trên Java 2D và 3D
Hoạt ảnh mức chi tiết (Level Of Detail Animations)
Mức chi tiết (Level Of Detail – LOD ) là một thuật ngữ chỉ kĩ thuật
biến đổi lượng chi tiết trong một đối tượng trực quan dựa trên một vài giá
trị của thế giới ảo. Ứng dụng thường gặp của kĩ thuật này là thay đổi mức
chi tiết của đối tượng dựa trên khoảng cách đối với người quan sát. Khi
khoảng cách tới đối tượng trực quan càng tăng thì càng ít chi tiết của đối
tượng đó được hiển thị. Do vậy, việc giảm độ phức tạp của đối tượng có
thể không ảnh hưởng đến kết quả hiển thị. Việc giảm bớt lượng chi tiết
của đối tượng khi chúng ở khoảng cách xa so với người quan sát sẽ làm
giảm khối lượng tính toán để tô trát. Nếu kĩ thuật này được áp dụng tốt sẽ
có thể tiết kiệm được đáng kể số lượng công thức tính toán mà vẫn không
làm mất đi nội dung của vật thể.
Lớp DistanceLOD cung cấp đối tượng hành vi LOD dựa trên khoảng
cách của vật thể đối với người quan sát. Một ứng dụng khác của LOD có
thể là thay đổi lượng chi tiết dựa trên tốc độ tô trát (hay tốc độ hiển thị,
được tính bằng số khung hình / giây) để giữ tỉ lệ khung hình là thấp nhất;
tốc độ của vật thể, hay mức độ chi tiết có thể được điều khiển với các
thiết lập của người dùng.
Mỗi đối tượng LOD có một hoặc nhiều hơn các đối tượng Switch
đóng vai trò đối tượng đích. Đối tượng Switch là một nhóm đặc biệt bao
gồm không, một hoặc nhiều hơn các con của chúng trong đồ thị khung
cảnh được dùng để hiển thị. Trong trường hợp sử dụng DistanceLOD,
việc lựa chọn con của đối tượng đích Switch được điều khiển bởi khoảng
cách của đối tượng DistanceLOD đối với người quan sát, dựa trên một tập
các ngưỡng khoảng cách.
Ngưỡng khoảng cách được xác định trong một mảng bắt đầu với
khoảng cách xa nhất, khi đó, con đầu tiên của đối tượng đích sẽ được sử
dụng. Con đầu tiên của đối tượng đích thông thường là đối tượng trực
quan chi tiết nhất. Khi khoảng cách của đối tượng DistanceLOD đối với
535
Lập trình đồ họa trên Java 2D và 3D
người quan sát lớn hơn ngưỡng thứ nhất, con thứ hai của đối tượng switch
sẽ được sử dụng. Các ngưỡng khoảng cách sau phải lớn hơn ngưỡng
khoảng cách trước và xác định khoảng cách mà con tiếp theo của đối
tượng đích được sử dụng. Do đó, số lượng con của đối tượng đích là
nhiều hơn số ngưỡng khoảng cách.
Nếu có nhiều hơn một đối tượng Switch được thêm vào với tư cách là
đích của đối tượng LOD , các đối tượng đích Switch sẽ được sử dụng
song song. Có nghĩa là các con của các đối tượng đích Switch có cùng chỉ
mục sẽ được lựa chọn đồng thời. Theo cách làm này, đối tượng trực quan
phức tạp có thể được biểu diễn bởi nhiều đối tượng hình học là con của
các đối tượng Switch khác nhau.
Sử dụng đối tượng DistanceLOD
Việc sử dụng đối tượng DistanceLOD tương tự việc sử dụng đối
tượng nội suy Interpolator , ngoại trừ việc không có đối tượng Alpha
nào cần sử dụng để điều khiển hoạt ảnh. Hoạt ảnh của đối tượng LOD
được điều khiển bởi khoảng cách tương đối của nó đối với người quan sát
trong thế giới ảo, theo cách này, việc sử dụng đối tượng DistanceLOD
giống với việc sử dụng đối tượng Billboard . Sử dụng đối tượng
DistanceLOD cũng cần phải thiết lập các ngưỡng khoảng cách. Hình 5-18
biểu diễn các bước để sử dụng DistanceLOD.
1. tạo các đối tượng Switch đích với khả năng
ALLOW_SWITCH_WRITE
2. tạo danh sách mảng các ngưỡng khoảng cách cho đối tượng
DistanceLOD
3. tạo đối tượng DistanceLOD sử dụng mảng các ngưỡng khoảng
cách
4. thiết lập đối tượng Switch đích cho đối tượng DistanceLOD
536
Lập trình đồ họa trên Java 2D và 3D
5. thiết lập giới hạn hoạt động cho đối tượng DistanceLOD
6. “lắp ghép” đồ thị khung cảnh, bao gồm cả việc thêm các con cho
các đối tượng Switch
Hình 5-18. Công thức sử dụng DistanceLOD để tạo hoạt ảnh
Các lỗi thường gặp khi sử dụng LOD
Mặc dù việc sử dụng đối tượng LOD không phải là phức tạp, nhưng
cũng có hai lỗi lập trình thường gặp sau. Lỗi thường xảy ra nhất là không
gộp đối tượng Switch đích vào đồ thị khung cảnh. Thiết lập đối tượng
Switch là đích của đối tượng DistanceLOD không tự động gộp nó vào đồ
thị khung cảnh.
Nếu khả năng ALLOW_SWITCH_WRITE không được thiết lập cho
đối tượng Switch đích thì khi chương trình được thực thi sẽ phát sinh lỗi
runtime. Còn nữa, nếu giới hạn hoạt động không được thiết lập, hoặc thiết
lập không chính xác thì LOD không điều khiển được đối tượng trực
quan. Giới hạn hoạt động thông thường được quy định bởi một đối tượng
BoundingSphere với bán kính đủ lớn để điều khiển được đối tượng trực
quan. Tương tự như các đối tượng hành vi khác, nếu không gộp LOD
vào đồ thị khung cảnh thì chương trình cũng sẽ không báo lỗi.
Có một giới hạn mà lớp LOD không vượt qua được chính là số lượng
quan sát mà chương trình có thể làm việc được. Nếu chương trình có
nhiều hơn một quan sát, LOD chỉ có thể điều khiển hoạt ảnh chính xác
cho chỉ một trong số các quan sát đó.
Ví dụ sử dụng DistanceLOD
Đoạn mã sau được trích từ phương thức createSceneGraph trong
chương trình DistanceLODApp. Chi tiết về chương trình này, xin xem
537
Lập trình đồ họa trên Java 2D và 3D
thêm phần phụ lục và đĩa chương trình kèm theo. Đoạn mã này đã được
đánh số tương ứng với các bước trong công thức sử dụng ở hình 5-18.
public BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
BoundingSphere bounds = new
BoundingSphere();
// create target TransformGroup with
Capabilities
TransformGroup objMove = new
TransformGroup();
objMove.setCapability(TransformGroup.ALLO
W_TRANSFORM_WRITE);
// create Alpha
Alpha alpha = new Alpha (-1,
Alpha.INCREASING_ENABLE
+ Alpha.DECREASING_ENABLE,
0, 0, 5000, 1000, 1000,
5000, 1000, 1000);
// specify the axis of translation
AxisAngle4f axisOfTra = new
AxisAngle4f(0.0f,1.0f,0.0f,(float)Math.PI/-2.0f);
Transform3D axisT3D = new Transform3D();
axisT3D.set(axisOfTra);
// create position interpolator
538
Lập trình đồ họa trên Java 2D và 3D
PositionInterpolator posInt
= new PositionInterpolator (alpha,
objMove, axisT3D, 0.0f, -35.0f);
posInt.setSchedulingBounds(bounds);
// create DistanceLOD target object
Switch targetSwitch = new Switch();
targetSwitch.setCapability(Switch.ALLOW_S
WITCH_WRITE);
// add visual objects of various levels
of detail to the target switch
Appearance sphereAppearA = new
Appearance();
ColoringAttributes sphereCAa = new
ColoringAttributes();
sphereCAa.setColor(0.1f, 0.8f, 0.1f);
sphereAppearA.setColoringAttributes(spher
eCAa);
Appearance sphereAppearB = new
Appearance();
ColoringAttributes sphereCAb = new
ColoringAttributes();
sphereCAb.setColor(0.8f, 0.1f, 0.1f);
sphereAppearB.setColoringAttributes(spher
eCAb);
539
Lập trình đồ họa trên Java 2D và 3D
targetSwitch.addChild(new Sphere(.40f, 0,
25, sphereAppearA));
targetSwitch.addChild(new Sphere(.40f, 0,
15, sphereAppearB));
targetSwitch.addChild(new Sphere(.40f, 0,
10, sphereAppearA));
targetSwitch.addChild(new Sphere(.40f, 0,
4, sphereAppearB));
// create DistanceLOD object
float[] distances = { 5.0f, 10.0f,
20.0f};
DistanceLOD dLOD = new
DistanceLOD(distances, new Point3f());
dLOD.addSwitch(targetSwitch);
dLOD.setSchedulingBounds(bounds);
if((targetSwitch.numChildren()-1) !=
dLOD.numDistances()){
System.out.println("DistanceLOD
not initialized properly");
System.out.println(targetSwitch.n
umChildren());
System.out.println(dLOD.numDistan
ces());
}
// assemble scene graph
540
Lập trình đồ họa trên Java 2D và 3D
objRoot.addChild(objMove); // target
TG of position interp to move vo
objRoot.addChild(posInt); // add
position interpolator
objMove.addChild(dLOD); // make
the bounds move with visual object
objMove.addChild(targetSwitch);// must
add target switch to scene graph too
// show a level 3 object up close for
comparison
Transform3D t3d = new Transform3D();
t3d.set(new Vector3f(0.6f, 0.0f, 0.0f));
TransformGroup tga = new
TransformGroup(t3d);
objRoot.addChild(tga);
tga.addChild(new Sphere(.40f, 0, 4,
sphereAppearB));
// show a level 0 object at a distance
for comparison
t3d.set(new Vector3f(-5.0f, 0.0f,
-35.0f));
TransformGroup tgb = new
TransformGroup(t3d);
objRoot.addChild(tgb);
tgb.addChild(new Sphere(.40f, 0, 25,
sphereAppearA));
541
Lập trình đồ họa trên Java 2D và 3D
// a white background is better for
printing images in tutorial
Background background = new Background();
background.setColor(1.0f, 1.0f, 1.0f);
background.setApplicationBounds(new
BoundingSphere());
objRoot.addChild(background);
// Let Java 3D perform optimizations on this
scene graph.
objRoot.compile();
return objRoot;
} // end of CreateSceneGraph method of
DistanceLODApp
Hình 5-19 biểu diễn biểu đồ đồ thị khung cảnh được tạo ra trong ví dụ
trên. Chú ý rằng, đối tượng Switch đích vừa là con của đối tượng
TransformGroup vừa được tham chiếu đến bởi đối tượng DistanceLOD.
Cả hai quan hệ này đều cần được xác lập.
542
Lập trình đồ họa trên Java 2D và 3D
Hình 5-19. Một phần biểu đồ đồ thị khung cảnh cho chương trình
DistanceLODApp
Hình 5-20 là hai cảnh được sinh từ chương trình DistanceLODApp.
Mỗi ảnh này có 2 khối cầu tĩnh và một khối cầu dịch chuyển (trong hình
bên phải, khối cầu bên trái nhất đã bị che khuất). Khối cầu dịch chuyển
được biểu diễn bằng một đối tượng DistanceLOD với 4 khối cầu có độ
phức tạp hình học khác nhau. Khối cầu nhỏ màu xanh là khối cầu chi tiết
nhất được sử dụng bởi đối tượng DistanceLOD ở khoảng cách cực đại.
Khối cầu lớn màu đỏ là khối cầu kém chi tiết nhất của đối tượng
DistanceLOD và ở khoảng cách cực tiểu. Hai khối cầu này được hiển thị
để làm mốc so sánh.
Trong chương trình này, đối tượng DistanceLOD được biểu diễn bởi các
khối cầu có màu sắc khác nhau để minh họa quá trình chuyển đổi.
Một đối tượng nội suy vị trí PositionInterpolator được sử dụng để
dịch chuyển đối tượng DistanceLOD theo hướng vuông góc với màn
hình. Khi đối tượng DistanceLOD dịch chuyển ra xa người quan sát, nó
sẽ chuyển đối tượng trực quan để hiển thị. Nếu không sử dụng các khối
543
Lập trình đồ họa trên Java 2D và 3D
cầu có màu sắc khác nhau sẽ rất khó khăn để nhận biết khi nào thì đối
tượng trực quan được chuyển.
Hình 5-20. Hai cảnh được sinh ra từ DistanceLODApp.
Giao diện lập trình ứng dụng DistanceLOD API
DistanceLOD định nghĩa một nút đối tượng hành vi LOD dựa
khoảng cách, hoạt động trên một nút nhóm Switch để lựa chọn một trong
số các con của nút Switch đó dựa vào khoảng cách của nút đối tượng
LOD so với người quan sát. Một mảng n giá trị khoảng cách đơn điệu
tăng, được xác định sao cho thành phần đầu tiên distances[0] tương ứng
với mức chi tiết cao nhất và thành phần cuối cùng distances[n-1] tương
ứng với mức chi tiết thấp nhất. Dựa vào khoảng cách thực từ người quan
sát đến nút DistanceLOD, n giá trị khoảng cách [0, n-1] lựa chọn n+1
mức chi tiết [0, n]. Nếu gọi khoảng cách từ người quan sát đến nút LOD
thì phương trình để xác định mức chi tiết (con của nút Switch) được lựa
chọn là:
0 nếu d <= distances[0]
1. i nếu distances[i-1] < d <= distances[i]
2. n nếu d > distances[n-1]
544
Lập trình đồ họa trên Java 2D và 3D
Chú ý rằng cả vị trí và mảng các giá trị khoảng cách đều được xác
định trong hệ tọa độ địa phương của nút hiện thời.
Các phương thức khởi tạo:
DistanceLOD()
Khởi tạo và khởi gán cho nút DistanceLOD giá trị mặc định.
DistanceLOD(float[] distances)
Khởi tạo và gán giá trị ban đầu cho nút DistanceLOD với mảng các
giá trị khoảng cách xác định và vị trí mặc định (0, 0, 0).
DistanceLOD(float[] distances, Point3f position)
Khởi tạo và khởi gán cho nút DistanceLOD với mảng các giá trị
khoảng cách và vị trí xác định.
Một số phương thức cung cấp bởi DistanceLOD:
int numDistances()
Trả về số lượng các ngưỡng khoảng cách.
void setDistance(int whichDistance, double distance)
Thiết lập ngưỡng khoảng cách LOD xác định.
void setPosition(Point3f position)
Thiết lập vị trí của nút LOD .
Morph
Các lớp nội suy thay đổi các thuộc tính trực quan khác nhau trong thế
giới ảo. Tuy nhiên, không có lớp nội suy nào cung cấp các khả năng thay
đổi hình dạng hình học của các đối tượng trực quan. Khả năng này do lớp
Morph cung cấp. Đối tượng Morph tạo hình dạng cho một đối tượng
trực quan thông qua việc nội suy từ một tập các đối tượng
GeometryArray. Theo đặc điểm này, lớp Morph giống với các lớp nội
suy. Tuy nhiên, Morph không phải là lớp nội suy, thậm chí, nó còn
không kế thừa lớp hành vi Behavior. Lớp Morph mở rộng từ lớp Node.
545
Lập trình đồ họa trên Java 2D và 3D
Trong chương 4, chúng ta đã làm quen với phương thức
processStimulus của đối tượng Behavior để thực hiện các thay đổi đối với
đồ thị khung cảnh thực hay đối với các đối tượng trong đồ thị khung cảnh
thực. Do không có lớp hành vi chuyên biệt sử dụng với đối tượng Morph
, các ứng dụng Morph phải tự xây dựng lớp hành vi riêng. Lớp Morph
được coi như một lớp hoạt ảnh hay lớp tương tác phụ thuộc vào sự mô
phỏng hành vi tương tác với đối tượng Morph .
Đối tượng Morph có thể được sử dụng để biến kim tự tháp thành
hình khối hộp, mèo thành chó, hoặc biến đổi bất kì hình dạng hình học
nào thành một dạng hình học khác. Hạn chế duy nhất là các đối tượng
hình học sử dụng cho việc nội suy phải cùng thuộc một lớp và là lớp con
của lớp GeometryArray, với cùng số lượng các đỉnh. Hạn chế về số lượng
đỉnh không phải là hạn chế lớn của Morph , trong đĩa chương trình kèm
theo, bạn đọc có thể thấy chương trình Pyramid2Cube.java, biến một kìm
tự tháp thành hình hộp.
Đối tượng Morph cũng có thể được sử dụng để tạo hoạt ảnh cho các
đối tượng trực quan (ví dụ: làm cho một người đi lại được, hay làm cho
bàn tay có thể cầm nắm được vật dụng…). Chương trình ví dụ tạo hoạt
ảnh cho bàn tay, Morphing.java cũng có thể tìm thấy trong đĩa chương
trình kèm theo. Chương trình ví dụ thứ 3, làm cho một dạng hình que đi
lại được sẽ được trình bày chi tiết trong phần sau.
Sử dụng đối tượng Morph
Để hiểu được cách sử dụng đối tượng Morph , trước hết, cần phải
biết các thức đối tượng Morph làm việc. Morph không quá phức tạp.
Đối tượng Morph lưu trữ một mảng các đối tượng GeometryArray với
mỗi đối tượng định nghĩa đặc tả hình học hoàn chỉnh cho một đối tượng
trực quan. Các đối tượng GeometryArray có thể được coi như là các
546
Lập trình đồ họa trên Java 2D và 3D
khung ảnh chính trong hoạt ảnh, hay chính xác hơn, là các hằng số trong
phương trình tính toán để tạo ra một đối tượng GeometryArray mới.
Ngoài mảng các đối tượng GeometryArray, một đối tượng Morph
còn có một mảng các trọng số, đây chính là các biến số trong phương
trình. Sử dụng các đối tượng GeometryArray cùng với các trọng số, đối
tượng Morph khởi tạo một đối tượng mảng hình học sử dụng giá trị
trọng số trung bình của tọa độ, màu sắc, …cung cấp bởi đối tượng
GeometryArray. Thay đổi các trọng số sẽ làm thay đổi hình dạng hình
học.
1. tạo một mảng các đối tượng GeometryArray
2. tạo một đối tượng Morph với khả năng
ALLOW_WEIGHTS_WRITE
3. lắp ghép đồ thị khung cảnh, bao gồm cả việc thêm các con cho đối
tượng đích Switch
Hình 5-21. Công thức sử dụng đối tượng Morph
Như đã trình bày, sử dụng đối tượng Morph không khó, tuy nhiên,
các bước này đúng cho cả hoạt ảnh và tương tác. Hoạt ảnh và tương tác
được cung cấp thông qua một đối tượng hành vi. Do đó, sử dụng đối
tượng Morph thường có nghĩa là viết một lớp hành vi. Cách viết lớp
hành vi đã được trình bày trong phần 4.2.1, nên ở đây không nói chi tiết.
Tất nhiên, Morph hoàn toàn có thể sử dụng mà không cần đối tượng
hành vi, nhưng nó sẽ không thể tạo ra được hoạt ảnh.
Ví dụ sử dụng Morph
Chương trình Morph này sử dụng một đối tượng hành vi tùy chỉnh
để sinh hoạt ảnh. Bước đầu tiên trong quá trình phát triển sẽ là xây dựng
lớp hành vi này.
547
Lập trình đồ họa trên Java 2D và 3D
Trong đối tượng hành vi được sử dụng để tạo hoạt ảnh cho đối tượng
Morph , phương thức processStimlus thay đổi trọng số của đối tượng
Morph . Trong ví dụ này processStimulus thiết lập các giá trị trọng số
dựa vào giá trị Alpha của một đối tượng Alpha. Hoạt động này xảy ra tại
mỗi khung ảnh được tô trát khi điều kiện kích hoạt đã được thỏa mãn.
Đoạn mã sau là đoạn mã xây dựng đối tượng hành vi tùy biến trong
chương trình MorphApp.
public class MorphBehavior extends Behavior{
private Morph targetMorph;
private Alpha alpha;
// the following two members are here for
effciency (no memory burn)
private double[] weights = {0, 0, 0, 0};
private WakeupCondition trigger = new
WakeupOnElapsedFrames(0);
// create MorphBehavior
MorphBehavior(Morph targetMorph, Alpha
alpha){
this.targetMorph = targetMorph;
this.alpha = alpha;
}
public void initialize(){
// set initial wakeup condition
this.wakeupOn(trigger);
}
548
Lập trình đồ họa trên Java 2D và 3D
public void processStimulus(Enumeration
criteria){
// don't need to decode event since
there is only one trigger
// do what is necessary
weights[0] = 0;
weights[1] = 0;
weights[2] = 0;
weights[3] = 0;
float alphaValue = 4f * alpha.value()
- 0.00001f;
int alphaIndex = (int) alphaValue;
weights[alphaIndex] = (double)
alphaValue - (double)alphaIndex;
if(alphaIndex < 3)
weights[alphaIndex + 1] = 1.0 -
weights[alphaIndex];
else
weights[0] = 1.0 -
weights[alphaIndex];
targetMorph.setWeights(weights);
// set next wakeup condition
this.wakeupOn(trigger);
}
} // end of class MorphBehavior
549
Lập trình đồ họa trên Java 2D và 3D
Lớp MorphBehavior tạo khung hoạt ảnh chính bằng cách sử dụng các
đối tượng GeometryArray hai lần tại một thời điểm theo một quá trình có
tính tuần hoàn. Lớp này thích hợp cho tất cả các hoạt ảnh sử dụng 4
khung ảnh chính và có thể thay đổi dễ dàng để khớp với số lượng khung
ảnh chính khác nhau.
Với lớp hành vi tùy chỉnh vừa viết, mọi việc còn lại chỉ là tạo khung
ảnh chính sử dụng cho hoạt ảnh. Hình 5-22 biểu diễn các hình vẽ tay
được sử dụng làm khung ảnh chính cho chương trình ví dụ. Muốn xây
dựng các khung ảnh đẹp hơn, có thể sử dụng các gói 3D.
Các hình có màu đen trông giống như là hai khung hình chính, mỗi
cái được lặp lại một lần, nhưng thực tế, chúng là bốn khung hình chính
riêng biệt. Sự khác nhau là ở thứ tự các đỉnh được xác định.
Đoạn mã sau được trích từ phương thức createSceneGraph của
chương trình MorphApp.java với các số đánh dấu các bước được chỉ ra
trong hình 5-21. Trong phương thức này, một đối tượng MorphBehavior,
một đối tượng Alpha và một đối tượng Morph được tạo ra, rồi được gộp
vào đồ thị khung cảnh. Các đối tượng khung ảnh chính GeometryArray
được tạo ra bởi một số phương thức khác. Toàn bộ chương trình có thể
tìm thấy trong đĩa chương trình kèm theo.
Hình 5-22. Các hình khung ảnh chính của MorphApp với đường quỹ đạo
của một đỉnh (đường mờ)
public BranchGroup createSceneGraph() {
550
Lập trình đồ họa trên Java 2D và 3D
// Create the root of the branch graph
BranchGroup objRoot = new BranchGroup();
Transform3D t3d = new Transform3D();
t3d.set(new Vector3f(0f, -0.5f, 0f));
TransformGroup translate = new
TransformGroup(t3d);
// create GeometryArray[] (array of
GeometryArray objects)
GeometryArray[] geomArray = new
GeometryArray[4];
geomArray[0] = createGeomArray0();
geomArray[1] = createGeomArray1();
geomArray[2] = createGeomArray2();
geomArray[3] = createGeomArray3();
// create morph object
Morph morphObj = new Morph(geomArray);
morphObj.setCapability(Morph.ALLOW_WEIGHT
S_WRITE);
// create alpha object
Alpha alpha = new Alpha(-1, 1, 0, 0,
2000, 100, 0, 0, 0, 0);
// create morph driving behavior
MorphBehavior morphBehav = new
MorphBehavior(morphObj, alpha);
551
Lập trình đồ họa trên Java 2D và 3D
morphBehav.setSchedulingBounds(new
BoundingSphere());
//assemble scene graph
objRoot.addChild(translate);
translate.addChild(morphObj);
objRoot.addChild(morphBehav);
Background background = new Background();
background.setColor(1f, 1f, 1f);
background.setApplicationBounds(new
BoundingSphere());
objRoot.addChild(background);
// Let Java 3D perform optimizations on this
scene graph.
objRoot.compile();
return objRoot;
} // end of CreateSceneGraph method of
MorphApp
Một chú ý thú vị rút ra từ ví dụ trên là nhiều hoạt ảnh khác nhau có
thể được tạo ra, sử dụng chính các khung hình chính được xây dựng ở
trên, với các lớp hành vi khác nhau. Hình 5-23 biểu diễn một cảnh sinh ra
bởi Morph3DApp. Trong chương trình này, 3 lớp hành vi khác nhau tạo
hoạt ảnh dựa trên một số hoặc tất cả đối tượng GeometryArray của
MorphApp. Chúng được gọi lần lượt (từ trái qua phải) là “In Place”,
“Tango”, và “Broken”.
552
Lập trình đồ họa trên Java 2D và 3D
Hình 5-23. Một cảnh trong chương trình Morph3App
Giao diện lập trình ứng dụng Morph API
Các phương thức khởi tạo:
Morph (GeometryArray[] geometryArrays)
Khởi tạo và khởi gán giá trị cho đối tượng Morph với một mảng đối
tượng GeometryArray xác định và một đối tượng null thuộc kiểu
Appearance.
Morph (GeometryArray[] geometryArrays, Appearance appearance)
Khởi tạo và khởi gán giá trị cho đối tượng Morph với một mảng đối
tượng GeometryArray và một đối tượng Appearance xác định.
Một số phương thức khác cung cấp bởi Morph :
void setAppearance(Appearance appearance)
Thiết lập thành phần hiển thị bề ngoài cho nút Morph .
void setGeometryArrays(GeometryArray[] geometryArrays)
Thiết lập thành phần geometryArrays cho nút Morph .
void setAppearanceOverrideEnable(boolean flag)
Thiết lập cờ để có nút lá AlternateAppearance làm bề ngoài hiển thị
cho nút Morph .
553
Lập trình đồ họa trên Java 2D và 3D
void setWeights(double[] weights)
Thiết lập vector trọng số Morph cho nút Morph hiện thời.
Các khả năng có thể thiết lập cho Morph :
ALLOW_APPEARANCE_READ | WRITE
Xác định rằng nút hiện thời cho phép đọc (ghi) thông tin
appearance của nó.
ALLOW_GEOMETRY_ARRAY_READ | WRITE
Xác định rằng nút hiện thời cho phép đọc (ghi) thông tin dạng hình
học của nó.
ALLOW_WEIGHTS_READ | WRITE
Xác định rằng nút hiện thời cho phép đọc (ghi) vector trọng số
Morph của nó.
Giao diện GeometryUpdater
Trong các phần trên, hoạt ảnh được tạo ra chủ yếu bằng cách di
chuyển các khối hình hình học, chứ không thay đổi hay tạo ra hình mới.
Ngoại trừ Morph tạo ra hình nội suy từ các hình cho trước. Java 3D API
giới thiệu giao diện GeometryUpdater, giao diện này cùng với
BY_REFERENCE geometry (chương 2) cho phép thay đổi hình dạng
hình học trong thời gian thực thi chương trình.
Với giao diện GeometryUpdater, người lập trình ứng dụng có thể tạo
ra bất cứ loại hoạt ảnh nào phụ thuộc vào việc thay đổi thông tin về dạng
hình học, kể cả các hoạt ảnh sử dụng kĩ thuật Billboard , level of detail
và Morph . Giao diện GeometryUpdater có tính mềm dẻo cao, cho phép
người lập trình tạo được nhiều hiệu ứng hơn so với các kĩ thuật trên.
Các ứng dụng có thể sử dụng GeometryUpdater bao gồm các kĩ thuật
hoạt ảnh chuẩn như kiến tạo các hệ động, các hệ phân tử; sinh bóng mờ tự
động hay các hiệu ứng đặc biệt như chớp… Do GeometryUpdater cho
554
Lập trình đồ họa trên Java 2D và 3D
phép truy cập đến dữ liệu từng đỉnh của đối tượng hình học nên khả năng
tạo hoạt ảnh là không có giới hạn.
Mặc dù có thể hiệu chỉnh dữ liệu đối tượng hình học BY_REFERENCE
mà không cần sử dụng đối tượng GeometryUpdater, nhưng cách làm này
cho kết quả hoặc là không thể dự đoán được hoặc là không ổn định.
Sử dụng GeometryUpdater
Để sử dụng được đối tượng GeometryUpdater cho các ứng dụng hình
học động, trước hết cần tạo đối tượng hình học BY_REFERENCE với các
khả năng thích hợp, tạo một lớp GeometryUpdater và lấy ra một đối
tượng thuộc lớp đó, rồi tạo một lớp hành vi tùy biến và cũng lấy ra một
đối tượng thuộc lớp đó. Công việc này không quá phức tạp như nhìn nhận
ban đầu.
Công việc tạo đối tượng hình học BY_REFERENCE không làm gì
nhiều hơn ngoài việc tạo ra một đối tượng hình học khác. Đối tượng
GeometryUpdater có nhiệm vụ hiệu chỉnh đối tượng hình học khi được
gọi. Đối tượng hành vi lập lịch gọi đối tượng GeometryUpdater trong ứng
dụng.
Chúng ta xem xét qua hai phương thức quan trọng của giao diện
GeometryUpdater. Hai phương thức này có cùng tên là updateData().
Phương thức updateData() thứ nhất phải được cài đặt chi tiết trong lớp
định nghĩa GeometryUpdater trong ứng dụng. Phương thức updateData()
thứ hai là phương thức của GeometryArray, phương thức này sẽ gọi
phương thức được cài đặt trong lớp GeometryUpdater.
void updateData(Geometry geometry)
Cập nhật dữ liệu hình học có thể truy cập bởi tham chiếu.
void updateData(GeometryUpdater updater)
555
Lập trình đồ họa trên Java 2D và 3D
Phương thức này gọi phương thức updateData của đối tượng
GeometryUpdater xác định để đồng bộ hóa cập nhật dữ liệu đối tượng
hình học được tham chiếu bởi đối tượng GeometryArray hiện thời.
Chương trình ví dụ hệ thống phân tử đài phun nước sử
dụng GeometryUpdater
Các hệ thống phân tử thông thường được sử dụng để mô hình nước,
khói, pháo hoa và các hiện tượng giống dạng lỏng khác. Trong một hệ
thống phân tử, có hai tham số thiết kế cơ bản: các phân tử sẽ có dạng như
thế nào, vị trí và hướng của nó sẽ được cập nhật ra sao. Các phân tử thông
thường được biểu diễn dưới dạng điểm hoặc đường, tuy nhiên, các dạng
hình học khác cũng có thể được sử dụng. Việc cập nhật chuyển động có
thể mô phỏng được hành vi tự nhiên của các đối tượng (cụ thể hơn là mô
phỏng các định luật vật lý) hay các chuyển động mong muốn khác. Thông
thường, một vài phần của mã chương trình sẽ có thành phần ngẫu nhiên
để tránh cho các phân tử hoạt động hoàn toàn giống nhau.
Trong chương trình ví dụ, các hạt nước được biểu diễn dưới dạng các
đoạn thẳng nhỏ với chuyển động tuân theo các quy luật vật lý (cụ thể là
chúng được gia tốc bởi trọng trường). Mỗi đoạn thẳng được xác định bởi
2 điểm.
Hình 5-24 là một chuỗi các ảnh thu được từ chương trình ví dụ
ParticleApp. Ảnh phía bên trái là đài nước trước khi các phân tử nước
được khởi tạo. Ảnh ở giữa là khi cột “nước” được khởi tạo trong đài
phun. Trong ảnh này, có tất cả khoảng 300 phần tử hoạt động. Hình ngoài
cùng bên phải là ảnh đài phun khi đã hoạt động một khoảng thời gian.
Trong hình này có khoảng 500 phần tử nước hoạt động.
556
Lập trình đồ họa trên Java 2D và 3D
Hình 5-24. Chuỗi hình ảnh thu từ chương trình ParticleApp
Trong chương trình ParticleApp.java, cả đài phun được quay quanh
trục để thể hiện tính 3 chiều tự nhiên của hoạt ảnh. Lớp hành vi tuỳ biến
Behavior và lớp GeometryUpdater là các lớp trong của lớp Fountain. Có
thể dùng một vài cách khác để thiết kế hoạt ảnh như trên. Tuy nhiên, sử
dụng các lớp này như là lớp trong sẽ biến Fountain trở thành một đối
tượng đồ hoạ hoạt ảnh hoàn chỉnh. Hơn nữa, do cả hai lớp Behavior và
Geometry đều chỉ được sử dụng cho riêng ứng dụng này, nên chẳng có lí
do gì để cho phép chúng được sử dụng bởi các lớp khác bên ngoài
Fountain.
Các đoạn mã sau tương ứng định nghĩa các đối tượng hình học, hành
vi và geometryUpdater cho ứng dụng ParticleApp. Ba đoạn mã này là
thành phần chính tạo nên hoạt ảnh trong ParticleApp.
Đoạn mã đầu tiên đưa ra định nghĩa cho lớp Fountain. Cụ thể, nó định
nghĩa một vài trường và tạo ra đối tượng hình học được dùng để biểu diễn
nước.
Ba trường (hay 3 thuộc tính) được định nghĩa gần thẻ . Trường
waterLines và baseElevation được sử dụng trong một vài phương thức
khác nhau nên chúng được khai báo là các thuộc tính của lớp Fountain.
waterLines là một tham chiếu đến đối tượng hình học LineArray, đối
tượng hình học của phần tử nước. baseElevation là giá trị toạ độ y của
557
Lập trình đồ họa trên Java 2D và 3D
chân đài phun. Trường thuộc tính thứ 3 giữ một tham chiếu đến đối tượng
WaterUpdater được tạo ra tại đây với mục đích là không làm tràn bộ nhớ.
Phương thức thứ nhất của lớp Fountain là createWaterGeometry().
Phương thức này có định nghĩa một biến nguyên N , N là số lượng các
phần tử nước (số các đoạn thẳng của LineArray) được dùng để biểu diễn
nước. Giá trị 1400 được gán cho N trong đoạn mã không có gì đặc biệt
ngoại trừ việc nó làm cho hoạt ảnh trông có vẻ hợp lý hơn. Gần như bất
cứ giá trị nào cũng có thể gán cho N. Càng nhiều phần tử được sử dụng để
tạo hoạt ảnh thì thời gian để xây dựng một khung hình hiển thị càng lớn.
Trong phương thức createWaterGeometry(), đối tượng LineArray
được tạo ra trên dòng mã gán nhãn . Số lượng các đỉnh là N*2, bởi mỗi
phần tử (mỗi đoạn thẳng) cần một đỉnh bắt đầu và một đỉnh kết thúc. Chú
ý rằng định dạng đỉnh bao gồm cả BY_REFERENCE.
Các phần tử nước được tạo hoạt ảnh bằng cách thay đổi giá trị toạ độ
đỉnh cho các đoạn thẳng tương ứng. Việc này chỉ có thể thực hiện được
nếu khả năng thích hợp được thiết lập. Dòng mã dán nhãn đầu tiên thiết
lập khả năng cho phép ghi dữ liệu đỉnh. Khả năng này cần phải được thiết
lập cho bất cứ ứng dụng nào sử dụng GeometryUpdater. Trong hầu hết
các ứng dụng sử dụng GeometryUpdater, khả năng đọc cũng cần phải
được thiết lập. Ứng dụng này đòi hỏi như vậy. Dòng mã dán nhãn thứ 2
có nhiệm vụ thiết lập khả năng đọc dữ liệu giá trị đỉnh.
Phụ thuộc ứng dụng và cách đối tượng GeometryUpdater được thiết
kế, thông tin dạng hình học nhất định ngoài dữ liệu đỉnh có thể cần để
thiết lập đối tượng Geometry. Ví dụ, nếu đối tượng GeometryUpdater
không “biết” được số đỉnh được sử dụng thì giá trị này phải được đọc từ
đối tượng Geometry đã được truyền cho nó. Tất nhiên, thông tin này chỉ
có thể đọc được nếu khả năng thích hợp được thiết lập. Dòng mã tiếp sau
hai dòng mã gán nhãn chịu trách nhiệm thiết lập khả năng đọc số lượng
đỉnh.
558
Lập trình đồ họa trên Java 2D và 3D
Các dòng mã còn lại trong đoạn mã sau khởi tạo toạ độ cho N đỉnh. Mỗi
đỉnh được khởi tạo toạ độ (0, baseElevation, 0), do đó ban đầu, không có
phần tử nào được nhìn thấy.
public class Fountain extends BranchGroup {
protected LineArray waterLines = null;
protected float baseElevation = -0.45f;
protected GeometryUpdater geometryUpdater =
new WaterUpdater();
Geometry createWaterGeometry() {
int N = 1400; // number of 'drops'
waterLines = new LineArray(N * 2,
LineArray.COORDINATES
| LineArray.BY_REFERENCE);
waterLines.setCapability(GeometryArray.ALLOW_REF_
DATA_WRITE);
waterLines.setCapability(GeometryArray.ALLOW_REF_
DATA_READ);
559
Lập trình đồ họa trên Java 2D và 3D
waterLines.setCapability(GeometryArray.ALLOW_COUN
T_READ);
float[] coordinates = new float[N * 3 *
2];
int p;
for (p = 0; p < N; p += 2) { // for each
particle
coordinates[p * 3 + 0] = 0.0f;
coordinates[p * 3 + 1] =
baseElevation;
coordinates[p * 3 + 2] = 0.0f;
coordinates[p * 3 + 3] = 0.0f;
coordinates[p * 3 + 4] =
baseElevation;
coordinates[p * 3 + 5] = 0.0f;
}
waterLines.setCoordRefFloat(coordinates);
// the following statements would be
redundant
// waterLines.setInitialCoordIndex(0);
// waterLines.setValidVertexCount(N*2);
return waterLines;
}
560
Lập trình đồ họa trên Java 2D và 3D
abstract class UpdateWaterBehavior extends
Behavior {
WakeupOnElapsedFrames w = null;
public UpdateWaterBehavior() {
w = new WakeupOnElapsedFrames(0);
}
public void initialize() {
wakeupOn(w);
}
public void processStimulus(Enumeration
critiria) {
waterLines.updateData(geometryUpdater);
wakeupOn(w);
} // end processStimulus
} // end class UpdateWaterBehavior
public class WaterUpdater implements
GeometryUpdater {
Random random;
public WaterUpdater() {
random = new Random();
561
Lập trình đồ họa trên Java 2D và 3D
}
public void updateData(Geometry geometry)
{
GeometryArray geometryArray =
(GeometryArray) geometry;
float[] coords =
geometryArray.getCoordRefFloat();
int N =
geometryArray.getValidVertexCount();
int i;
for (i = 0; i < N; i += 2) { // for
each particle
if (coords[i * 3 + 1] >
baseElevation) { // update active
// particles
coords[i * 3 + 0] +=
coords[i * 3 + 0] - coords[i * 3 + 3]; // x1
coords[i * 3 + 1] +=
coords[i * 3 + 1] - coords[i * 3 + 4]
- 0.01f; // y1
coords[i * 3 + 2] +=
coords[i * 3 + 2] - coords[i * 3 + 5]; // z1
coords[i * 3 + 3] =
(coords[i * 3 + 0] + coords[i * 3 + 3]) / 2; //
x2
562
Lập trình đồ họa trên Java 2D và 3D
coords[i * 3 + 4] =
(coords[i * 3 + 1] + coords[i * 3 + 4] + 0.01f) /
2;// y2
coords[i * 3 + 5] =
(coords[i * 3 + 2] + coords[i * 3 + 5]) / 2; //
z2
if (coords[i * 3 + 1] <
baseElevation) { // if particle
// below base
coords[i * 3 + 0] =
0.0f; // x1
coords[i * 3 + 1] =
baseElevation; // y1
coords[i * 3 + 2] =
0.0f; // z1
coords[i * 3 + 3] =
0.0f; // x2
coords[i * 3 + 4] =
baseElevation; // y2
coords[i * 3 + 5] =
0.0f; // z2
}
} else { // an inactive particle
if (random.nextFloat() >
0.8) { // randomly start a drop
coords[i * 3 + 0] =
0.03f * (random.nextFloat() - 0.5f); // x1
563
Lập trình đồ họa trên Java 2D và 3D
coords[i * 3 + 1] =
0.14f * random.nextFloat()
+
baseElevation; // y1
coords[i * 3 + 2] =
0.03f * (random.nextFloat() - 0.5f); // z1
} // end if
} // end if-else
} // end for loop
}
}
}
Đoạn mã sau định nghĩa lớp UpdateWaterBehavior, một lớp mở rộng
từ lớp Behavior. Đây là đoạn mã dễ nhất trong ứng dụng
GeometryUpdater. Lớp Behavior điều khiển hoạt ảnh bằng cách gọi
phương thức updateGeometry của đối tượng hình học được tạo hoạt ảnh
khi phương thức processStimulus của nó được gọi.
Định nghĩa lớp UpdateWaterBehavior bao gồm trường w – một tham
chiếu đến đối tượng WakeupOnElasedFrames - được sử dụng để kích
hoạt đối tượng hành vi. Đối tượng WakeupOnElasedFrames được tạo
trong phương thức khởi tạo của UpdateWaterBehavior bắt đầu từ dòng
mã được dán nhãn . Phương thức initialize() của lớp
UpdateWaterBehavior, bắt đầu từ dòng mã , thiết lập điều kiện đánh
thức ban đầu cho đối tượng hành vi.
Phương thức processStimulus(), bắt đầu từ dòng mã gán nhãn , định
nghĩa các hành động của đối tượng hành vi đáp ứng lại các sự kiện đánh
thức nó. Trong trường hợp này, phương thức updateData() được gọi và
truyền tham số geometryUpdater cho waterLines.
564
Lập trình đồ họa trên Java 2D và 3D
abstract class UpdateWaterBehavior extends
Behavior {
WakeupOnElapsedFrames w = null;
public UpdateWaterBehavior() {
w = new WakeupOnElapsedFrames(0);
}
public void initialize() {
wakeupOn(w);
}
public void processStimulus(Enumeration
critiria) {
waterLines.updateData(geometryUpdater);
wakeupOn(w);
} // end processStimulus
} // end class UpdateWaterBehavior
Đoạn mã sau định nghĩa lớp GeometryUpdater. GeometryUpdater
dịch chuyển các phần tử nước bằng cách thay đổi dữ liệu tọa độ của
chúng.
Lớp GeometryUpdater trong ứng dụng này có một hàm khởi tạo và
một phương thức. Trong hàm khởi tạo, nhãn , một đối tượng Random
được tạo ra để sử dụng trong phương thức thường duy nhất của lớp,
phương thức updateData().
565
Lập trình đồ họa trên Java 2D và 3D
Phương thức updateData(), nhãn , tạo hoạt ảnh cho các phần tử
nước. Thông thường, không phải tất cả các phần tử đều hoạt động tại
cùng một thời điểm. Phần tử nào không hoạt động sẽ có tọa độ y trùng với
tọa độ y của chân đài phun (baseElevation). Nếu một phần tử có tọa độ y
bằng với baseElevation, nó được coi là không hoạt động và vì thế, phần tử
này không di chuyển. Ban đầu, tất cả các phần tử nước đều không hoạt
động.
Xét hệ phần tử này một thời gian ngắn sau khi khởi động, khi này, đã
có một vài phần tử hoạt động, số còn lại thì chưa. Mỗi lần updateData()
được gọi, tiến trình hoạt ảnh sẽ thu thập thông tin liên quan về đối tượng
hình học để cập nhật. Trên dòng mã , tham số Geometry được ép kiểu
thành GeometryArray. Trên dòng mã , một tham chiếu đến dữ liệu tọa
độ đỉnh được lấy ra. Dòng thu thập số lượng đỉnh.
Chú ý rằng, ứng dụng này có thể chạy hiệu quả hơn bằng cách tính
toán các thông tin này một lần rồi lưu trữ nó trong các trường của đối
tượng. Tuy nhiên, hiệu quả đạt được cũng chỉ hạn chế và làm cho đoạn
mã không sử dụng lại được. Lớp Geometry này có thể được sử dụng cho
đài phun với các kích thước khác nhau.
Khi đã có những bước chuẩn bị thích hợp, phương thức updateData() xét
mỗi phần tử tại một thời điểm nào đó trong vòng lặp . Với mỗi phần tử
hoạt động (xác định bằng cách so sánh tọa độ y của nó với
baseElevation), chương trình sẽ tính toán chuyển động cong dạng parabol
của nó. Tọa độ đỉnh đầu tiên của một phần tử được gán các giá trị thích
hợp để mô hình chuyển động, sau đó, tọa độ cũ của đỉnh thứ nhất sẽ được
gán cho đỉnh thứ hai.
Câu lệnh if trên dòng kiểm tra xem phần tử đã vượt qua
baseElevation hay chưa. Nếu điều kiện này thỏa mãn, phần tử đó sẽ
ngừng hoạt động bởi chương trình cập nhật giá trị tọa độ cả hai đỉnh của
nó về giá trị ban đầu, giá trị xác định phần tử không hoạt động.
566
Lập trình đồ họa trên Java 2D và 3D
Một phần các phần tử không hoạt động, phần else của điều kiện trên dòng
, được ngẫu nhiên khởi tạo bởi điều kiện trên dòng . Trong ví dụ này,
một lượng trung bình khoảng 20% các phần tử không hoạt động sẽ được
khởi tạo.
public class WaterUpdater implements
GeometryUpdater {
Random random;
public WaterUpdater() {
random = new Random();
}
public void updateData(Geometry geometry)
{
GeometryArray geometryArray =
(GeometryArray) geometry;
float[] coords =
geometryArray.getCoordRefFloat();
int N =
geometryArray.getValidVertexCount();
int i;
for (i = 0; i < N; i += 2) { // for
each particle
if (coords[i * 3 + 1] >
baseElevation) { // update active
567
Lập trình đồ họa trên Java 2D và 3D
// particles
coords[i * 3 + 0] +=
coords[i * 3 + 0] - coords[i * 3 + 3]; // x1
coords[i * 3 + 1] +=
coords[i * 3 + 1] - coords[i * 3 + 4]
- 0.01f; // y1
coords[i * 3 + 2] +=
coords[i * 3 + 2] - coords[i * 3 + 5]; // z1
coords[i * 3 + 3] =
(coords[i * 3 + 0] + coords[i * 3 + 3]) / 2; //
x2
coords[i * 3 + 4] =
(coords[i * 3 + 1] + coords[i * 3 + 4] + 0.01f) /
2;// y2
coords[i * 3 + 5] =
(coords[i * 3 + 2] + coords[i * 3 + 5]) / 2; //
z2
if (coords[i * 3 + 1] <
baseElevation) { // if particle
// below base
coords[i * 3 + 0] =
0.0f; // x1
coords[i * 3 + 1] =
baseElevation; // y1
coords[i * 3 + 2] =
0.0f; // z1
568
Lập trình đồ họa trên Java 2D và 3D
coords[i * 3 + 3] =
0.0f; // x2
coords[i * 3 + 4] =
baseElevation; // y2
coords[i * 3 + 5] =
0.0f; // z2
}
} else { // an inactive particle
if (random.nextFloat() >
0.8) { // randomly start a drop
coords[i * 3 + 0] =
0.03f * (random.nextFloat() - 0.5f); // x1
coords[i * 3 + 1] =
0.14f * random.nextFloat()
+
baseElevation; // y1
coords[i * 3 + 2] =
0.03f * (random.nextFloat() - 0.5f); // z1
} // end if
} // end if-else
} // end for loop
}
}
Cột nước trong hoạt ảnh trông sẽ đẹp hơn nếu các đoạn thẳng biểu
diễn mỗi phần tử loại bỏ được răng cưa. Việc này có thể thực hiện được
bằng cách thêm đối tượng LineAttributes với
setLineAntialiasedEnable(true) vào thành phần Appearance của phần tử
nước. Tuy nhiên, lưu ý rằng, việc làm này có thể phải trả giá bởi tốc độ
xử lý hoạt ảnh.
569
Lập trình đồ họa trên Java 2D và 3D
570
Lập trình đồ họa trên Java 2D và 3D
Tài liệu tham khảo
Trong quá trình làm cuốn tutorial về lập trình đồ họa trong Java 2D
& 3D chúng em có tham khảo các tài liệu sau:
1. Slide bài giảng Kĩ thuật đồ họa và hiện thực ảo của ThS.Lê Tấn
Hùng.
2. The Java Tutorial, 2nd Volume. Available online at:
3. The 2D Text Tutorial. Available online from the Java Developer
Connection:
DText/
3. The Java 2D Sample Programs. Available online at:
4. The Java 2D Demo. Available from the Java 2D website:
5. The Java 3D document at:
571
Các file đính kèm theo tài liệu này:
- Lap-Trinh-Java2D-and-3D.pdf