Đ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().
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.
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.
303 trang |
Chia sẻ: oanh_nt | Lượt xem: 1674 | Lượt tải: 0
Bạn đang xem trước 20 trang tài liệu Chuyên đề Lập trình đồ họa trên Java 2D và 3D, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
c cho đối tượng Billboard
lắp ghép đồ thị
Hình 5-15. Công thức sử dụng đối tượng Billboard tạo hoạt ảnh
Các lỗi thường gặp khi sử dụng Billboard
Mặc dù việc sử dụng đối tượng Billboard không hề phức tạp, nhưng cũng có hai lỗi lập trình thường gặp sau. Lỗi thứ nhất xảy ra do đối tượng đích TransformGroup thiết lập lại hoàn toàn giá trị của nó mỗi khi được cập nhật. Do đó, đối tượng TransformGroup này không thể sử dụng để định vị đối tượng trực quan. Nếu dùng đối tượng đích để định vị, hiệu ứng Billboard vẫn hoạt động, tuy nhiên, trong lần cập nhật quay đầu tiên, thông tin vị trí của đối tượng đích sẽ bị mất và đối tượng trực quan sẽ được hiển thị tại vị trí ban đầu.
Nếu đối tượng đích không được thiết lập thuộc tính ALLOW_TRANSFORM_WRITE khi chương trình được thực thi sẽ phát sinh lỗi runtime. Nếu danh giới làm việc không được thiết lập hay thiết lập không chính xác thì đối tượng Billboard sẽ không điều khiển được đối tượng trực quan. Danh giới làm việc thông thường được xác định bởi đối tượng BoundingSphere với phạm vi làm việc đủ lớn để bao gồm đối tượng trực quan.
Có một giới hạn khi sử dụng lớp Billboard : trong các ứng dụng với nhiều hơn một quan sát, mỗi đối tượng Billboard chỉ điều khiển hoạt ảnh chính xác cho chỉ một quan sát. Với một vài ứng dụng thì khả năng cung cấp của Billboard là đảm bảo, còn với các ứng dụng khác, đặc điểm này là một giới hạn lớn. Java3D API phiên bản 1.2 giới thiệu lớp OrientedShape3D để vượt qua giới hạn này. Đối tượng OrientedShape3D cung cấp các chức năng giống với chức năng của đối tượng Billboard nhưng có thêm khả năng xử lý nhiều quan sát cùng một lúc. Billboard quay quanh một trục hoặc một điểm. Trong trường hợp sử dụng khác, đối tượng Billboard điều khiển một đối tượng TransformGroup sao cho trục z dương của đối tượng TransformGroup và các con của nó luôn đối diện với người quan sát. Do Billboard hướng trục z+ của đối tượng đích về phía người quan sát nên nó không thể dùng để quay vật thể quanh trục z. Vì lý do này, nếu một trục quay của đối tượng được xác định là (0, 0, z) thì đơn giản Billboard không làm gì cả.
Chương trình ví dụ sử dụng Billboard
Chương trình ví dụ BillboardApp tạo một thế giới ảo với cây cối sử dụng kĩ thuật Billboard . Mặc dù cây cối trong chương trình này được tạo bởi các hình thô (hình tam giác phẳng) nhưng chúng không hề hiển thị như các đối tượng 2 chiều.
Hai đối tượng TransformGroup được sử dụng cho mỗi cây trong ví dụ này. Một đối tượng TransformGroup, TGT, đơn giản chỉ dịch các cây thành tọa độ vị trí trong ứng dụng. Giá trị của TGT không bị thay đổi trong thời gian thực thi của ứng dụng. Đối tượng TransformGroup thứ hai, TGR, điều khiển hoạt động quay của cây. TGR là đối tượng đích của Billboard .
public BranchGroup createSceneGraph(SimpleUniverse su) {
// Create the root of the branch graph
TransformGroup vpTrans = null;
BranchGroup objRoot = new BranchGroup();
Vector3f translate = new Vector3f();
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();
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);
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.
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.
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
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 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.
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();
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 .
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 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.
tạo các đối tượng Switch đích với khả năng ALLOW_SWITCH_WRITE
tạo danh sách mảng các ngưỡng khoảng cách cho đối tượng DistanceLOD
tạo đối tượng DistanceLOD sử dụng mảng các ngưỡng khoảng cách
thiết lập đối tượng Switch đích cho đối tượng DistanceLOD
thiết lập giới hạn hoạt động cho đối tượng DistanceLOD
“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 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.ALLOW_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
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_SWITCH_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(sphereCAa);
Appearance sphereAppearB = new Appearance();
ColoringAttributes sphereCAb = new ColoringAttributes();
sphereCAb.setColor(0.8f, 0.1f, 0.1f);
sphereAppearB.setColoringAttributes(sphereCAb);
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.numChildren());
System.out.println(dLOD.numDistances());
}
// assemble scene graph
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));
// 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.
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 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à:
nếu d <= distances[0]
i nếu distances[i-1] < d <= distances[i]
n nếu d > distances[n-1]
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.
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 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.
tạo một mảng các đối tượng GeometryArray
tạo một đối tượng Morph với khả năng ALLOW_WEIGHTS_WRITE
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.
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);
}
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
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() {
// 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_WEIGHTS_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);
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”.
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 .
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 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)
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.
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 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.
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);
waterLines.setCapability(GeometryArray.ALLOW_COUNT_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;
}
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();
}
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
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
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.
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().
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.
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
// 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
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.
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:
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:
Các file đính kèm theo tài liệu này:
- DAN011.doc