Giáo trình Lập trình đồ họa trên Java 2D và 3D
Enhanced Graphics, Text, and imaging
Nếu các phiên bản trước của AWT chỉ cung cấp các gói tô trát(rendering) đơn giản chỉ phù hợp cho việc rendering các trang HTML đơn giản,mà không đáp ứng đủ cho các dạng đồ hoạ,văn bản và ảnh phức tạp. Thì Java 2D™ API cung cấp gói tô trát đẩy đủ các tính năng bằng
cách mở rộng AWT để hỗ trợ cho đồ hoạ và các thao tác rendering. Ví dụ như thông qua các lớp đồ hoạ cho phép vẽ hình chữ nhật,hình ôval,và các đa giác. Đồ hoạ 2D tăng cường về khái niệm của phép tô trát hình học bằng cách cung cấp một cơ chế cho phép rendering ảo của bất kì bề mặt hình học nào.Tương tư như vậy với Java 2D API bạn có thể vẽ các dạng đường với bất kì độ rộng và dạng hình học nào.
Dạng hình học được cung cấp thông qua các phần thực thi implementations của Shape interface trong Graphic class, như hình chữ nhật 2D và Elip 2D. Các đường cong và hình cung cũng thuộc phần implementations của Shape. Các kiểu vẽ và tô cũng được cung cấp thông qua phần thực thi implementations của giao tiếp Paint và Stroke interfaces, ví dụ BasicStroke, GradientPaint, TexturePaint,và Color.
AffineTransform định nghĩa các phép biến đổi trong toạ độ 2 chiều, gồm có phép lấy tỉ lệ,phép chuyển đổi toạ độ ,phép quay và phép xén cắt.
Khung nhìn được định nghĩa bởi các phương thức giống nhau của lớp Shape mà chúng được dùng để định nghĩa các khung nhìn chung,ví dụ như Rectangle2D và GeneralPath.
Thành phần màu sắc được cung cấp bởi các phương thức của lớp Composite, ví dụ AlphaComposite.
Một kiểu Font chữ thì được định nghĩa bởi các collection của Glyphs.
Tóm tắt nội dung tài liệu: Giáo trình Lập trình đồ họa trên Java 2D và 3D
Lời nói đầu Sự phát triển của khoa học, kĩ thuật, nghệ thuật, kinh doanh, và công nghệ luôn luôn phụ thuộc vào khả năng truyền đạt thông tin của chúng ta, hoặc thông qua các bit dữ liệu lưu trữ trong microchip hoặc thông qua giao tiếp bằng tiếng nói. Câu châm ngôn từ xa xưa “một hình ảnh có giá trị hơn cả vạn lời" hay "trăm nghe không bằng một thấy" cho thấy ý nghĩa rất lớn của hình ảnh trong việc chuyển tải thông tin. Hình ảnh bao giờ cũng được cảm nhận nhanh và dễ dàng hơn, đặc biệt là trong trường hợp bất đồng về ngôn ngữ. Do đó không có gì ngạc nhiên khi mà ngay từ khi xuất hiện máy tính, các nhà nghiên cứu đã cố gắng sử dụng nó để phát sinh các ảnh trên màn hình. Trong suốt gần 50 năm phát triển của máy tính, khả năng phát sinh hình ảnh bằng máy tính của chúng ta đã đạt tới mức mà bây giờ hầu như tất cả các máy tính đều có khả năng đồ họa.Đồ họa máy tính là một trong những lĩnh vực lí thú nhất và phát triển nhanh nhất của tin học. Ngay từ khi xuất hiện, đồ họa máy tính đã có sức lôi cuốn mãnh liệt, cuốn hút rất nhiều người ở nhiều lĩnh vực khác nhau như khoa học, nghệ thuật, kinh doanh, quản lí, ... Tính hấp dẫn và đa dạng của đồ họa máy tính có thể được minh họa rất trực quan thông qua việc khảo sát các ứng dụng của nó. Ngày nay, đồ họa máy tính được sử dụng trong rất nhiều lĩnh vực khác nhau như công nghiệp, thương mại, quản lí, giáo dục, giải trí, Số lượng các chương trình đồ họa ứng dụng thật khổng lồ và phát triển liên tục.Trong lĩnh vực công nghiệp,CAD(computer-aided design) đã được sử dụng hầu hết trong việc thiết kế các cao ốc, ô tô, máy bay, tàu thủy, tàu vũ trụ, máy tính,Trong lĩnh vực giải trí,nghệ thuật, đồ họa máy tính giúp ta tạo ra các chương trình trò chơi,các kĩ xảo điện ảnh cho các nhà làm phim,hay ngay chính giao diện đồ họa đã làm nâng cao khả năng giao tiếp giữa người và máy tính. Để có thể làm được những ứng dụng đồ họa hữu ích cho cuộc sống,trước hết chúng ta cần phải có một nền tảng vững chắc về lập trình đồ họa.Có rất nhiều ngôn ngữ hỗ trợ lập trình đồ họa máy tính,mỗi ngôn ngữ được xây dưng trên những thư viện đồ họa riêng,có những thế mạnh riêng.Và khi nói về lập trình đồ họa,chúng ta không thể không nói đến ngôn ngữ Java,một ngôn ngữ rất mạnh trong lĩnh vực này.Với mục đích nghiên cứu,tìm hiểu và mang đến cho những ai muốn tìm hiều về lập trình đồ họa một tài liệu thiết thực,nhóm chúng em đã chọn đề tài làm tutorial về lập trình đồ họa trên nền tảng ngôn ngữ lâp trình Java,dựa trên các tài liệu training trên mạng của hãng Sun.Vì là lần đầu làm tài liệu tham khảo nên chúng em không tránh khỏi sai sót.Chúng em mong thầy xem xét và góp ý cho tài liệu này.Chúng em chân thành cảm ơn. Phần 1 Lập trình đồ họa với Java 2D Chương 1 Tổng quan về Java 2D API Java 2D™ API tăng cường về khả năng đồ hoạ, văn bản và ảnh của Abstract Windowing Toolkit (AWT), giúp phát triển về giao diện người sủ dụng và ứng dụng về JAVA trong một số lĩnh vực mới.Cùng với khả năng mạnh về đồ hoạ ,phông chữ và ảnh trong các API, thì Java 2D API còn hỗ trợ những thuộc tính và thành phần màu sắc đã được nâng cao, và thành công trong việc biểu diễn các bề mặt hình học tuỳ ý và một kiểu tô trát chuẩn cho máy in và thiết bị hiển thị. Java 2D API cũng cho phép tạo ra các thư viện đồ hoạ mở rộng,như các thư viện của CAD-CAM và các thư viện tạo hiệu ứng đặc biệt cho ảnh và đồ hoạ, cũng như việc tạo ra các bộ lọc đọc/viết file ảnh và đồ hoạ.Khi được sử dụng kết hợp với Java Media Framework Java Media APIs khác ,thì Java 2D APIs có thể được sử dụng để tạo ra và hiển thị hiện thực ảo và các dạng đa phương tiện khác. Java Animation và Java Media Framework APIs dưa trên Java 2D API để hỗ trợ việc tô trát(rendering). 1.1 Enhanced Graphics, Text, and imaging Nếu các phiên bản trước của AWT chỉ cung cấp các gói tô trát(rendering) đơn giản chỉ phù hợp cho việc rendering các trang HTML đơn giản,mà không đáp ứng đủ cho các dạng đồ hoạ,văn bản và ảnh phức tạp. Thì Java 2D™ API cung cấp gói tô trát đẩy đủ các tính năng bằng cách mở rộng AWT để hỗ trợ cho đồ hoạ và các thao tác rendering. Ví dụ như thông qua các lớp đồ hoạ cho phép vẽ hình chữ nhật,hình ôval,và các đa giác. Đồ hoạ 2D tăng cường về khái niệm của phép tô trát hình học bằng cách cung cấp một cơ chế cho phép rendering ảo của bất kì bề mặt hình học nào.Tương tư như vậy với Java 2D API bạn có thể vẽ các dạng đường với bất kì độ rộng và dạng hình học nào. Dạng hình học được cung cấp thông qua các phần thực thi implementations của Shape interface trong Graphic class, như hình chữ nhật 2D và Elip 2D. Các đường cong và hình cung cũng thuộc phần implementations của Shape. Các kiểu vẽ và tô cũng được cung cấp thông qua phần thực thi implementations của giao tiếp Paint và Stroke interfaces, ví dụ BasicStroke, GradientPaint, TexturePaint,và Color. AffineTransform định nghĩa các phép biến đổi trong toạ độ 2 chiều, gồm có phép lấy tỉ lệ,phép chuyển đổi toạ độ ,phép quay và phép xén cắt. Khung nhìn được định nghĩa bởi các phương thức giống nhau của lớp Shape mà chúng được dùng để định nghĩa các khung nhìn chung,ví dụ như Rectangle2D và GeneralPath. Thành phần màu sắc được cung cấp bởi các phương thức của lớp Composite, ví dụ AlphaComposite. Một kiểu Font chữ thì được định nghĩa bởi các collection của Glyphs. 1.2 Rendering Model Kiểu tô trát đồ họa đơn giản không có gì thay đổi khi thêm vào Java 2D™ APIs. Để tô trát đồ họa thì phải thiết lập các thông số về đồ họa và gọi phương thức của đối tượng Graphics. Trong Java 2D API, lớp Graphics2D mở rộng lớp Graphics để hỗ trợ thêm nhiều thuộc tính đồ họa và cung cấp thêm các phương thức cho quá trình tô trát. The Java 2D API tự động cân chỉnh những sai khác trong các thiết bị tô trát và cung cấp một kiểu tô trát thống nhất cho các dạng thiết bị khác nhau. Tại tầng ứng dụng,quá trình tô trát là giống nhau cho dù thiết bị cuối đó là màn hình hay máy in. Với Java 2 SDK, version 1.3 , Java 2D API còn hỗ trợp cho các môi trường đa màn hình (multi-screen environments). 1.2.1 Coordinate Systems Java 2D API bao hàm hai hệ tọa độ: Không gian người sủ dụng là hệ tọa độ logic và độc lập với thiết bị. Các ứng dụng thường sủ dụng hệ tọa độ này,tất cả các dạng hình học được tô trát bằng Java 2D đều được xác định trong hệ tọa độ này. Không gian thiết bị là hệ tọa độ phụ thuộc thiết bị,ứng với từng loại thiết bị cuối mà có hệ tạo độ khác nhau. Bằng ứng dụng máy ảo một cửa sổ hiển thị có thể mở rộng thêm nhiều thiết bị hiển thị vậy lý tạo nên môi trường đa màn hiển thị, hệ tọa độ của thiết bị này được sủ dụng làm hệ tọa độ của máy ảo sẽ được lấy làm hệ tọa độ cho tất cả các màn hiể thị còn lại. Hệ thống Java 2D sẽ tự động thực hiện những phép chuyển đổi cần thiết giữa không gian người sử dụng và không gian thiết bị . Mặc dù hệ tọa độ cho màn hình rất khác đối với hệ tọa độ cho máy in nhưng sự khác biệt này trong suốt đối với các ứng dụng. 1.2.1.1 User Space Hệ tọa độ này được chỉ ra trong hình 1-1. (0,0) x y Figure 1-1 User Space Coordinate System Không gian người sử dụng biểu diễn một cách trừu tượng đồng nhất cho các hệ tạo độ của tất cả các thiết bị có liên quan.Còn không gian thiết bị cho một thiết bị cụ thể có thể có cùng gốc tọa độ và hướng của các trục hoặc là có thể không.Ngoài ra các tọa độ của không gian người sử dụng có thể chuyển đổi một cách tự động vào không gian thiết bị phù hợp mỗi khi một đối tượng đồ họa được tô trát,thường thì các chương trình driver của các thiết bị thực hiện điều này. 1.2.1.2 Device Space Java 2D API định nghĩa 3 mức thông tin cấu hình để hỗ trợ cho việc chuyển đổi từ không gian người sử dụng sang không gian thiết bị. Thông tin này được chứa trong 3 lớp : • GraphicsEnvironment • GraphicsDevice • GraphicsConfiguration Hai trong số chúng sẽ biểu diễn tất cả các thông tin cần thiết cho việc xác định thiết bi tô trát và cho việc chuyển đổi tọa độ từ không gian người sử dụng sang không gian thiết bị. Ứng dụng có thể truy cập thông tin này nhưng không nhất thiết phải thực hiện bất kì phép chuyển đổi nào giũa hai hệ tọa độchuyển . Lớp GraphicsEnvironment mô tả tập các thiết bị mà đựợc chấp nhận bởi các ứng dụng Java trong một môi trường cụ thể.Các thiết bị sử dụng cho quá trình tô trát gồm có màn hình , má in và các bộ đệm ảnh.Lớp GraphicsEnvironment cũng bao gồm tập các font phù hợp trong môi trường đó. Lớp GraphicsDevice mô tả thiết bị tô trát như màn hình hoặc máy in.Mỗi cấu hình phù hợp của thiết bị được biểu diễn bởi lớp GraphicsConfiguration. Ví dụ,một thiết bị hiển thị SVGA có thể thực hiện trong một số chế độ màu như : 640x480x16 colors, 640x480x256 colors, and 800x600x256 colors. Màn hình SVGA được biểu diễn bởi một đối tượng GraphicsDevice và ứng với mỗi chế độ màu của màn hình SVGA sẽ được biểu diễn bởi một đối tượng GraphicsConfiguration.Một đối tượng kiểu GraphicsEnvironment có thể bao gồm 1 hay nhiều đối tượng GraphicsDevices. Ngược lại ,mỗi đối tượng GraphicsDevice có thể có 1 hay nhiều đối tượng kiểu GraphicsConfigurations. 1.2.2 Transforms Java 2D API có một chế độ chuyển tọa độ thống nhất. Tất cả các phép chuyển tọa độ,bao gồm các phép chuyển từ không gian người sử dụng đến không gian của thiết bị,đều được biểu diễn bởi đối tượng kiểu AffineTransform .AffineTransform định nghĩa các luật cho những thao tác trên các hệ tọa độ bằng các ma trận(các phép biến đổi ảnh bằng các bộ lọc). Cũng có thể thêm đối tượng AffineTransform cho các dạng đồ họa bằng các phép xoay ,lấy tỉ lệ ,chuyển đổi hoặc cắt tọa độ cho một mặt hình học ,văn bản hay ảnh khi chúng được tô trát.Các phép biến đổi được thêm vào này sẽ được ứng dụng cho các đối tượng đồ họa được tô trát trong trường hợp đó phép bién đổi này được thực hiện khi chuyển từ tọa độ không gian người sử dụng sang không gian của thiết bị. 1.2.3 Fonts Một xâu thường được hiểu là tập các kí tự tao thành.Khi một xâu được vẽ, hình dạng của nó sẽ được xác định bởi font mà chúng ta chọn.Tuy nhiên, hình dạng mà font sử dụng để hiển thị lên màn hình xâu đó không phải lúc nào cũng giống với hình dáng của mỗi kí tự riêng biệt. ví dụ sự kết hợp của 2 hay nhiều kí tự thường được thay thế bởi một hình dạng kí hiêu nào đó được gọi là ligature. Các hình dạng mà một phông chữ sử dụng để biểu diễn các kí tự trong một xâu được gọi là glyphs. Một font có thể biểu diễn một kí tự như chữ thường hoặc chữ hoa bằng cách sử dụng nhiều glyphs, hoặc biểu diễn các liên kết kí tự như fi chỉ biểu diễn bởi 1 glyph. Trong Java 2D API, một glyph chỉ đơn giản là một dạng (Shape) mà có thể đựoc thao tác và tô trát một cách giống nhau như bất kì với các dạng khác Shape. Một font có thể được xem như tập các glyph. Một font đơn có thể có rất nhiều kiểu khác nhau như kểi chữ đậm ,vừa,nghiêng ,gôtíchtất cả các kiểu chữ này trong một font có cùng thiết kế in và có thể ví như chúng là các thành viên trong cùng một gia đình. Hay nói cách khác ,một nhóm các glyphs với các kiểu riêng biệt tạo nên một dạng font,nhóm các dạng font tao nên môt họ font ,họ các font tạo nên một tập font và tập này có sẵn trong một đối tượng GraphicsEnvironment cụ thể nào đó. Trong Java 2D API, các font được xác định bởi một tên mà mô tả một dạng font riêng biệt nào đó,ví dụ Helvetica Bold. Nhưng điều này lại khác với fần mềm JDK 1.1 ,các font được mô tả bằng các tên lôgíc mà ánh xạ trên các dạng font khác nhau fụ thuộc liệu các dạng font đó có sắn trong môi trường đang xét không.Như để tương thích cho điều đó ; the Java 2D API hỗ trợ việc xác định các font bằng tên lôgíc cũng như bằng tên dạng font. Sử dụng Java 2D API, có thể soạn thảo và tô rát các xâu bao gồm nhiều font thuộc các họ font,hình dạng,kich thước, và thâm chí cả ngôn ngữ khác nhau. Các dạng của văn bản được lưu riêng biệt một cách lôgíc với sự sắp xếp các văn bản.Các đối tượng Font được sử dụng để mô tả các hình dạng hiển thị của font, và thông tin sắp xếp được lưu trong đối tượng TextLayout và TextAttributeSet . Việc lưu giứ các font và thông tin sắp xếp riêng biệt nhau làm dễ dàng hơn cho việc sử dụng cùng font chữ nhưng khác về cấu hình sắp xếp. 1.2.4 Images Ảnh được tạo nên từ tập các pixel . Một điểm ảnh hay một pixel sẽ định nghĩa thể hiện của một ảnh tại vùng quan sát của màn hình . Một mảng hai chiều của điểm ảnh được gọi là một raster. Thể hiện của một điểm ảnh có thể được định nghĩa một cách trực tiếp hoặc như là một chỉ số trong bảng màu dành cho ảnh. Trong ảnh màu đa màu (hơn 256 màu) các điểm ảnh thường trực tiếp biểu diễn luôn màu sắc và các đặc trưng khác cho mỗi vùng hiển thị của ảnh. Những ảnh như vậy sẽ có kích thước lớn hơn ảnh màu mà hiển thị bằng chỉ số(indexed-color images), nhưng chúng nhìn sẽ thật hơn . Trong ảnh màu hiển thị bằng chỉ số (indexed-color image), những màu sắc của ảnh sẽ bị giới hạn bởi bảng màu , và thường số lượng màu trong bảng màu sẽ ít hơn sovới ảnh thật. Tuy nhiên các ảnh khi được lưu như tập các chỉ số màu sẽ làm cho kích thước ảnh nhỏ đi.Định dạng này thường được dùng cho những ảnh có 256 màu. Ảnh trong Java 2D API có hai thành phần chính • Dữ liệu ảnh thô(các điểm ảnh) • Những thông tin cần thiết cho quá trình chuyển đổi các điểm ảnh Các luật cho việc chuyển đổi các điểm ảnh được lưu bởi đối tượng ColorModel. Đối với một điểm ảnh để hiển thị cần phải đi kèm với một kiểu màu sắc. Một dải màu là một thành phần của không gian màu sắc dành cho ảnh.Ví dụ các thành phần màu đỏ là các daỉ màu trong ảnh RGB . Một điểm ảnh trong ảnh dùng màu trực tiếp có thể được hiểu như một tập . Gói java.awt.image bao gồm một số phương thức ColorModel cho các biểu diễn thành phần điểm ảnh. Một đối tượng ColorSpace chứa các luật để sao cho tập các giá trị màu tương ứng với một màu sắc nhất định .Các phương thức của ColorSpace trong java.awt.color sẽ biẻu diễn các không gian màu sắc thông dụng, gồm có không gian màu RGB và gray scale. Lưu ý rằng một không gian màu không phải là một tập các màu sắc mà tập này định nghĩ các luật để có thể chuyển đổi các giá trị màu thành các màu tương ứng thông dịch. Việc chia không gian màu sắc thành các chế độ màu sẽ tạo nên sự linh hoạt hơn trong việc biểu diễn và chuyển đổi từ phép biểu diễn màu này sang một phếp biểu diẽn màu khác. 1.2.5 Fills and Strokes Với Java 2D API, có thể tô các hình băng cách sử dụng các kiểu but khác nhau và các kiểu tô khác nhau. Vì các chữ xét cho cùng cũng được biểu diễn bằng tập các glyph, nên các xâu kí tự cũng có thể được vẽ và tô. Các kiểu bút được định nghĩa băng các đối tượng kiểu Stroke.Có thể xác định độ rộng cũng như các nét cho các đường thẳng và đường cong. Các kiểu tô được định nghĩa bởi các phương thức thuộc đối tượng Paint. Lớp Color có sắn trong các phiên bản trước của AWT đây là một dạng đơn giản của đối tượng Paint được sử dụng để định nghĩa tô màu trơn (solid color). Java 2D API cung cấp thêm hai phương thức mới cho Paint là TexturePaint và GradientPaint. TexturePaint định nghĩa kiểu tô dùng mẩu ảnh đơn giản(simple image fragment ) mà được lặp như nhau. GradientPaint định nghĩa một kiểu tô màu loang(gradient) giữa hai màu. Trong Java 2D, rendering một đường nét của một hình và tô hình đó bằng một kiểu nào đó được thực hiên hai thao tác riêng biệt: • Sử dụng một trong các thuật toán draw để render các đường nét của hình sử dụng các kiểu bút được xác định bởi các thuộc tính của Stroke và kiểu tô được xác định bởi thuộc tính Paint. • Sử dụng phương thức fill để tô các vùng trong của hình với kiểu ... ượ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:
File đính kèm:
- giao_trinh_lap_trinh_do_hoa_tren_java_2d_va_3d.doc