Α.Τ.Ε.Ι ΚΑΒΑΛΑΣ ΣΧΟΛΗ ΔΙΟΙΚΗΣΗΣ ΚΑΙ ΟΙΚΟΝΟΜΙΑΣ ΤΜΗΜΑ ΔΙΑΧΕΙΡΙΣΗΣ ΠΛΗΡΟΦΟΡΙΩΝ ΑΛΓΟΡΙΘΜΟΙ ΓΡΑΦΙΚΩΝ ΣΕ JAVA ΣΑΡΛΙΔΟΥ ΔΕΣΠΟΙΝΑ ΤΖΙΝΑΡΑ ΠΑΣΧΑΛΙΑ ΕΠΟΠΤΗΣ ΚΑΘΗΓΗΤΗΣ: ΠΑΠΑΔΗΜΗΤΡΙΟΥ ΣΤΕΡΓΙΟΣ ΜΑΙΟΣ 2011 Εκπονηθείσα πτυχιακή εργασία για την κτήση του βασικού πτυχίου
ΠΕΡΙΕΧΟΜΕΝΑ ΚΕΦΑΛΑΙΟ 1 1 1.1 Γ Ρ Α Μ Μ Ε Σ, Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ε Σ Κ Α Ι Ε Ι Κ Ο Ν Ο- Σ Τ Ο Ι Χ Ε Ι Α 2 1.2 ΤΑ ΟΡΙΑ ΤΩΝ ΓΕΜΙΣΜΕΝΩΝ ΠΕΡΙΟΧΩΝ 6 1.3 Λ Ο Γ Ι Κ Ε Σ Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ε Σ 7 1.3.1 Η κατεύθυνση του άξονα Υ 7 1.3.2 Συνεχείς και Διακριτές Συντεταγμένες 7 1.4 Α Ν Ι Σ Ο Τ Ρ Ο Ι Κ Α Ι Ι Σ Ο Τ Ρ Ο Π Ι Κ Ο Ι Μ Ε Θ Ο Δ Ο Ι Χ Α Ρ Τ Ο Γ Ρ Α Φ Η Σ Η Σ 11 1.4.1 Χαρτογραφόντας ένα συνεχές διάστημα σε μια ακολουθία ακέραιων αριθμών 11 1.4.2 Ανιστοτροπική Μέθοδος Χαρτογράφησης 12 1.4.3 Ισοτροπική Μέθοδος Χαρτογράφησης 14 1.5 Ο Ρ Ι Ζ Ο Ν Τ Α Σ Ε Ν Α Π Ο Λ Υ Γ Ω Ν Ο Χ Ρ Η Σ Ι Μ Ο Π Ο Ι Ω Ν Τ Α Σ Τ Ο Π Ο Ν Τ Ι Κ Ι 17 Α Σ Κ Η Σ Ε Ι Σ 21 ΚΕΦΑΛΑΙΟ 2 25 2.1 Δ Ι Α Ν Υ Σ Μ Α Τ Α 26 2.2 Ε Σ Ω Τ Ε Ρ Ι Κ Ο Γ Ι Ν Ο Μ Ε Ν Ο 28 2.3 Ο Ρ Ι Ζ Ο Υ Σ Ε Σ 28 2.4 Δ Ι Α Ν Υ Σ Μ Α Τ Ι Κ Ο Γ Ι Ν Ο Μ Ε Ν Ο 31 2.5 Ο Π Ρ Ο Σ Α Ν Α Τ Ο Λ Ι Σ Μ Ο Σ Τ Ω Ν Τ Ρ Ι Ω Ν Σ Η Μ Ε Ι Ω Ν 32 2.5.1 Μία Εναλλακτική, Δισδιάστατη Λύση 34 2.5.2 Μια Χρήσιμη Μέθοδος Java 34 2.6 Π Ο Λ Υ Γ Ω Ν Α 35 2.7 Τ Ο Ε Μ Β Α Δ Ο Ν Ε Ν Ο Σ Π Ο Λ Υ Γ Ω Ν Ο Υ 36 2.7.1 Κώδικας Java 36 2.8 Ε Λ Ε Γ Χ Ο Σ Γ Ι Α Ε Υ Ρ Ε Σ Η Σ Η Μ Ε Ι Ω Ν Μ Ε Σ Α Σ Ε Τ Ρ Ι Γ Ω Ν Ο 37 2.8.1 Μια Εναλλακτική Mέθοδος insidetriangle 38 2.9 Ε Λ Ε Γ Χ Ο Σ Γ Ι Α Ε Υ Ρ Ε Σ Η Σ Η Μ Ε Ι Ω Ν Μ Ε Σ Α Σ Ε Π Ο Λ Υ Γ Ω Ν Ο 38 2.9.1 Η μέθοδος contains της Java κλάσης Polygon 40 2.10 Ε Λ Ε Γ Χ Ο Σ Γ Ι Α Ε Υ Ρ Ε Σ Η Σ Η Μ Ε Ι Ω Ν Σ Ε Ε Υ Θ Ε Ι Α 40 2.10.1 Έλεγχος για εύρεση σημείου σε ένα ευθύγραμμο τμήμα 41 2.11 Α Π Ο Σ Τ Α Σ Η Μ Ε Τ Α Ξ Υ Ε Ν Ο Σ Σ Η Μ Ε Ι Ο Υ Κ Α Ι Μ Ι Α Σ Ε Υ Θ Ε Ι Α Σ 42 2.12 Π Ρ Ο Β Ο Λ Η Ε Ν Ο Σ Σ Η Μ Ε Ι Ο Υ Σ Ε Μ Ι Α Ε Υ Θ Ε Ι Α 43 2.13 Τ Ρ Ι Γ Ω Ν Ο Π Ο Ι Η Σ Η Π Ο Λ Υ Γ Ω Ν Ω Ν 45 Α Σ Κ Η Σ Ε Ι Σ 49 ΚΕΦΑΛΑΙΟ 3 52 3.1 Π Ο Λ Λ Α Π Λ Α Σ Ι Α Σ Μ Ο Σ Μ Η Τ Ρ Α Σ 53 3.2 Γ Ρ Α Μ Μ Ι Κ Ο Σ Μ Ε Τ Α Σ Χ Η Μ Α Τ Ι Σ Μ Ο Σ 54 3.2.1 Περιστροφή 55 3.2.2 Ένα Παράδειγμα Προγράμματος 55 3.2.3 Κλιμάκωση 58 3.2.4 Διάτμηση 58
3.3 Μ Ε Τ Α Φ Ρ Α Σ Ε Ι Σ 59 3.4 Ο Μ Ο Γ Ε Ν Ε Ι Σ Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ε Σ 59 3.5 Α Ν Τ Ι Σ Τ Ρ Ο Φ Ο Ι Μ Ε Τ Α Σ Χ Η Μ Α Τ Ι Σ Μ Ο Ι Κ Α Ι Α Ν Τ Ι Σ Τ Ρ Ο Φ Η Μ Η Τ Ρ Α Σ 61 3.6 Π Ε Ρ Ι Σ Τ Ρ Ο Φ Η Π Ε Ρ Ι Ε Ν Ο Σ Α Υ Θ Α Ι Ρ Ε Τ Ο Υ Σ Η Μ Ε Ι Ο Υ 62 3.6.1 Μια Εφαρμογή 63 3.7 Α Λ Λ Α Ζ Ο Ν Τ Α Σ Τ Ο Σ Υ Σ Τ Η Μ Α Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ω Ν 66 3.8 Π Ε Ρ Ι Σ Τ Ρ Ο Φ Ε Σ Π Ε Ρ Ι 3 D Α Ξ Ο Ν Ω Ν Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ω Ν 67 3.9 Π Ε Ρ Ι Σ Τ Ρ Ο Φ Η Π Ε Ρ Ι Ε Ν Ο Σ Α Υ Θ Α Ι Ρ Ε Τ Ο Υ Α Ξ Ο Ν Α 68 3.9.1 Εφαρμογή 71 Α Σ Κ Η Σ Ε Ι Σ 75 ΚΕΦΑΛΑΙΟ 4 77 4.1 Ο Α Λ Γ Ο Ρ Ι Θ Μ Ο Σ Τ Ο Υ B R E S E N H A M Γ Ι Α Σ Χ Ε Δ Ι Α Σ Μ Ο Γ Ρ Α Μ Μ Ω Ν 78 4.2 Δ Ι Π Λ Α Σ Ι Α Ζ Ο Ν Τ Α Σ Τ Η Ν Τ Α Χ Υ Τ Η Τ Α Σ Χ Ε Δ Ι Α Σ Η Σ Γ Ρ Α Μ Μ Η Σ 83 4.3 Κ Υ Κ Λ Ο Ι 89 4.4 Α Π Ο Κ Ο Π Η Γ Ρ Α Μ Μ Ω Ν C O H E N - S U T H E R L A N D 94 4.5 Α Π Ο Κ Ο Π Η Π Ο Λ Υ Γ Ω Ν Ω Ν S U T H E R L A N D - H O D G M A N 99 4.6 Κ Α Μ Π Υ Λ Ε Σ B E Z I E R 105 4.6.1 Κατασκευάζοντας Λείες Καμπύλες από Τμήματα Καμπυλών 113 4.6.2 Σημειογραφία Μήτρας 113 4.6.3 Καμπύλες 3D 115 4.7 Π Ρ Ο Σ Α Ρ Μ Ο Γ Η Κ Α Μ Π Υ Λ Η Σ B - S P L I N E 116 Α Σ Κ Η Σ Ε Ι Σ 120 ΒΙΒΛΙΟΓΡΑΦΙΑ
Κεφάλαιο 1 Στοιχειώδης Έννοιες Αυτό το βιβλίο είναι κυρίως για προγραμματισμό γραφικών και μαθηματικά. Αντί να ασχοληθούμε με γενικά γραφικά θέματα για τους τελικούς χρήστες ή το πώς να χρησιμοποιήσουμε λογισμικό γραφικών, θα ασχολήθουμε με πιο κύρια θέματα που απαιτούνται για τον προγραμματισμό γραφικών. Σε αυτό το κεφάλαιο αρχικά θα κατανοήσουμε και θα εκτιμήσουμε την διακριτική φύση των προβαλλόμενων γραφικών στις οθόνες των υπολογιστών. Στην συνέχεια θα δούμε ότι οι συντεταγμένες x- και y-, επίσης γνωστές και ως συντεταγμένες συσκευής, δεν είναι απαραίτητο να είναι πάντα αριθμοί εικονοστοιχείων. Σε πολλές εφαρμογές οι λογικές συντεταγμένες είναι πιο βολικές, υπό την προυπόθεση ότι μπορούμε να τις μετατρέψουμε σε συντεταγμένες συσκευής. Ειδικά στα δεδομένα που εισάγονται μέσω ποντικιού, χρειαζόμαστε επίσης την αντίστροφη μετατροπή, όπως θα δούμε στο τέλος του κεφαλαίου. 1
1.1 Γ Ρ Α Μ Μ Ε Σ, Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ε Σ Κ Α Ι Ε Ι Κ Ο Ν Ο Σ Τ Ο Ι Χ Ε Ι Α Ο πιο βολικός τρόπος για να προσδιορίσουμε ένα ευθύγραμμο τμήμα στην οθόνη του υπολογιστή είναι δίνοντας τις συντεταγμένες των δύο πλευρών του. Στα μαθηματικά, οι συντεταγμένες είναι πραγματικοί αριθμοί, αλλά απλοί τρόποι σχεδίασης γραμμών ίσως να απαιτούν αυτοί οι αριθμοί να είναι ακέραιοι. Αυτό ισχύει για παράδειγμα στην γλώσσα Java, την οποία θα χρησιμοποιήσουμε σε αυτό το βιβλίο. Το Java Abstract Windows Toolkit (AWT) μας δίνει την κλάση Graphics που περιέχει την μέθοδο drawline, την οποία θα χρησιμοποιήσουμε ως εξής για να σχεδιάσουμε το ευθύγραμμο τμήμα που ενώνει το Α και το Β. g.drawline(xa, ya, xb, yb) ; Το πλαίσιο γραφικών g στην αρχή της μεθόδου δίνεται ως παράμετρος της μεθόδου paint που χρησιμοποιούμε, και τα τέσσερα ορίσματα του drawline είναι ακέραιοι αριθμοί, που κυμαίνονται από το μηδέν μέχρι κάποια μέγιστη τιμή. Το παραπάνω μας δίνει ακριβώς την ακριβώς ίδια γραμμή με το: g.drawline(xb, yb, xa, ya) ; Θα χρησιμοποιήσουμε τώρα δηλώσεις όπως την παραπάνω σε ένα ολοκληρωμένο πρόγραμμα Java. Ευτυχώς, δεν θα χρειαστεί να γράψετε εσείς αυτά τα προγράμματα, μιας και όπως έχει αναφερθεί στην εισαγωγή, υπάρχουν διαθέσιμα στο διαδίκτυο. Θα χρειαστεί επίσης να εγκαταστήσετε το Java Development Kit (JDK), το οποίο επίσης μπορείτε να κατεβάσετε από την ακόλουθη διεύθυνση: http://java.sun.com Αν δεν είστε εξοικειωμένοι με την Java, πρέπει πρώτα να συμβουλευτείτε άλλα βιβλία εκτός από αυτό που κρατάτε στα χέρια σας, όπως κάποια από αυτά που συμπεριλαμβάνονται στην Βιβλιογραφία. Το ακόλουθο πρόγραμμα δημιουργεί το μεγαλύτερο δυνατό ορθογώνιο σε έναν καμβά. Το κόκκινο χρώμα χρησιμοποιείται για να διαχωρίσει το ορθογώνιο από τα όρια του πλαισίου. //RedRect.java:THe lanregest possible rectangle in red import java.awt.*; import java.awt.event*; public class RedRect extends Frame { public static void main(string[] args){new RedRect(); 0 χ rwidth RedRect() { super("redrect"); addwindowlistener(new WindowAdapter() { public void windowclosing(windowevent e) {System.exit(0);); setsize (200, 100); add("center", new CvRedRect()); show(); 2
class CvRedRect extends Canvas { public void paint(graphics g) { Dimension d = getsize(); int maxx = d.width -1, maxy = d.height - 1; g.drawstring("d.width = " + d.width, 10, 30); g.drawstring("d.height = " + d.height, 10, 60); g.setcolor(color.red); g.drawrect(0, 0, maxx,maxy); Η δήλωση στο drawrect που είναι σχεδόν στο τέλος του προγράμματος, έχει το ίδιο αποτέλεσμα με τις εξής τέσσερις γραμμές: g.drawline(0, 0, maxx, 0); // Top edge g.drawline(maxx, 0, maxx, maxy); // Right edge g.drawline(maxx, maxy, 0, maxy); // Bottom edge g.drawline(0, maxy, 0, 0); //Feft edge Το πρόγραμμα περιέχει δύο κλάσεις: RedRect: CvRedRect: Η κλάση για το πλαίσιο που επίσης χρησιμοποιείται για να κλείσουμε την εφαρμογή. Η κλάση για τον καμβά στην οποία προβάλλουμε τα γραφικά. Παρόλαυτα, αφού συντάξουμε το πρόγραμμα εισάγοντας την εντολή javac RedRect.java παρατηρούμε ότι έχουν δημιουργηθεί τρία αρχεία.class: RedRect.class, CvRedRect.class και RedRect$1.class. Το τρίτο αρχείο λέγεται ανώνυμη κλάση, μιας και δεν έχει όνομα στο πρόγραμμα. Δημιουργείται από τις δύο γραμμές του προγράμματος addwindowlistener (newwindowadapter() {public void windowclosing(windowevent e){system.exit(0);); που δίνει την δυνατότητα στον χρήστη να το τερματίσει με τον συνηθισμένο τρόπο. Θα μπορούσαμε να είχαμε γράψει τον ίδιο κωδικό προγράμματος ως addwindowlistener ( new WindowAdapter() { pablic void windowclosing(windowevent e) { System.exit(0); ); για να δείξουμε πιο καθαρά την δομή αυτού του μέρους. Το όρισμα της μεθόδου addwindowlistener πρέπει να είναι ένα αντικείμενο που να υλοποιεί το περιβάλλον WindowsListener. Αυτό υποδηλώνει ότι αυτή η κλάση πρέπει να ορίζει επτά μεθόδους, μια εκ των οποίων είναι το windowclosing. Η βασική κλάση WindowAdapter ορίζει αυτές τις επτά μεθόδους ως συναρτήσεις do-nothing. Στο πιο πάνω τμήμα, το όρισμα του addwindowlistener υποδηλώνει ένα αντικείμενο μιας ανώνυμης υποκλάσης του WindowAdapter. Σε αυτή την κλάση αγνοούμε την 3
μέθοδο windowclosing. Περαιτέρω συζήτηση γι αυτόν τον συμπαγή κώδικα για την διαχείριση συμβάντων υπάρχει στο Παράρτημα Β. Ο κατασκευαστής RedRect δείχνει ότι το μέγεθος του πλαισίου είναι καθορισμένο ως 200 x 100. Εάν δεν αλλάξουμε αυτό το μέγεθος (σύροντας μια γωνία ή μια πλευρά του παραθύρου, το μέγεθος του καμβά είναι ελαφρώς μικρότερο. Μετά την μεταγλώτιση τρέχουμε το πρόγραμμα πληκτρολογώντας την εντολή Java RedRect η οποία μας δίνει αυτό που φαίνεται στο Σχήμα 1.1 Σχήμα 1.1: Μεγαλύτερο δυνατό ορθογώνιο και διαστάσεις του καμβά Η κενή περιοχή σε ένα πλαίσιο, το οποίο χρησιμοποιούμε για την έξοδο γραφικών, ονομάζεται Client Rectangle στον προγραμματισμό των Windows. Συνεχώς θα χρησιμοποιούμε έναν καμβά γι αυτό, το οποίο είναι υποκλάση, όπως η CvRedRect στο πρόγραμμα RedRect.java, της AWT κλάσης Canvas. Αν αντί γι αυτό εμφανίσουμε απευθείας το αποτέλεσμα στο πλαίσιο, θα είχαμε πρόβλημα με το σύστημα συντεταγμένων: η αρχή του θα ήταν στην πάνω αριστερή γωνία του πλαισίου. Με άλλα λόγια, οι συντεταγμένες x αυξάνουν από αριστερά προς τα δεξιά και οι συντεταγμένες y από πάνω προς τα κάτω. Αν και υπάρχει μέθοδος (getinsets) για να μας δώσει τα μήκη και από τα τέσσερα όρια του πλαισίου έτσι ώστε να υπολογίσουμε τις διαστάσεις του client rectangle μόνοι μας, προτιμούμε να χρησιμοποιήσουμε έναν καμβά. Τα μικροσκοπικά στοιχεία της οθόνης στα οποία μπορούμε να ορίσουμε ένα χρώμα ονομάζονται εικονοστοιχεία (pixels), και οι ακέραιες τιμές x- και y- ονομάζονται συντεταγμένες συσκευής. Αν και υπάρχουν 200 εικονοστοιχεία σε μια οριζόντια γραμμή του πλαισίου, μόνο τα 192 από αυτές είναι στον καμβά, τα υπόλοιπα 8 χρησιμοποιούνται για το αριστερό και το δεξί όριο. Σε μία κάθετη γραμμή, υπάρχουν 100 εικονοστοιχεία για όλο το πλαίσιο αλλά μόνο 72 για τον καμβά. Τα υπόλοιπα 27 εικονοστοιχεία χρησιμοποιούνται για την μπάρα του τίτλου και για τα πάνω και κάτω όρια. Μιας και αυτά τα νούμερα μπορεί να διαφέρουν ανάλογα με την εφαρμογή Java και ο χρήστης μπορεί να αλλάξει το μέγεθος του παραθύρου, είναι επιθυμητό το πρόγραμμα μας να μπορεί να καθορίσει τις διαστάσεις του καμβά. Το κάνουμε αυτό χρησιμοποιώντας την μέθοδο getsize της κλάσης Component, η οποία είναι υπερκλάση της Canvas. Οι γραμμές προγράμματος που ακολουθούν είναι με την μέθοδο paint και μας δείχνουν πως μπορούμε να βρούμε τις διαστάσεις του καμβά και πως τις ερμηνεύουμε. Dimension d=getsize(); Int maxx = d.windth -1, maxy = d.height -1; Η μέθοδος getsize του Component (υπερκλάση της Canvas) μας δίνει τον αριθμό των εικονοστοιχείων στις οριζόντιες και κάθετες γραμμές του καμβά. Μιας και αρχίζουμε να μετράμε από το μηδέν, οι μεγαλύτεροι αριθμοί, maxx και maxy, σε αυτές τις γραμμές είναι ένα λιγότερο 4
απ ότι οι αριθμοί των εικονοστοιχείων. Θυμηθείτε ότι είναι παρόμοιο με τους πίνακες στην Java και στην C. Για παράδειγμα, αν γράψουμε int[] a = new int[8]; η μεγαλύτερη δυνατή τιμή είναι 7 και όχι 8. Το σχήμα 1.2 μας το δείχνει αυτό για έναν πολύ μικρό καμβά, ο οποίος έχει μόνο 8 εικονοστοιχεία πλάτος και 4 εικονοστοιχεία ύψος, Σχήμα 1.2: Εικονοστοιχεία ως συντεταγμένες σε έναν καμβά 8 x 4 (με maxx = 7 και maxy = 3) δίνοντας μας μία κατά πολύ μεγεθυμένη δομή πλέγματος. Μας δείχνει επίσης ότι η γραμμή που ενώνει τα σημεία (0, 0) και (7, 3) προσεγγίζεται από ένα σύνολο των 8 εικονοστοιχείων. Οι μεγάλες κουκίδες κοντά στην γραμμή υποδηλώνουν εικονοστοιχεία τα οποία είναι ρυθμισμένα στο χρώμα του προσκηνίου. Από προεπιλογή, το χρώμα στο προσκήνιο είναι μαύρο και το χρώμα στο φόντο είναι άσπρο. Αυτά τα οχτώ εικονοστοιχεία γίνονται μαύρα ως αποτέλεσμα της δήλωσης g.drawline(0, 0, 7, 3); Στο πρόγραμμα RedRect.java, χρησιμοποιήσαμε την ακόλουθη δήλωση για την drawrect μέθοδο (αντί για τέσσερις δηλώσεις στην drawline) g.drawrect(0, 0, maxx, maxy) Γενικότερα, η δήλωση g.drawrect(x, y, w, h); σχεδιάζει ένα ορθογώνιο με (x, y) ως τις πάνω αριστερά γωνίες του και (x + w, y + h) ως τις κάτω δεξιά γωνίες του. Με άλλα λόγια, το τρίτο και το τέταρτο όρισμα της μεθόδου drawrect καθορίζει το πλάτος και το ύψος, και όχι την κάτω δεξιά γωνία του ορθογωνίου που θα σχεδιαστεί. Σημειώστε ότι αυτό το ορθογώνιο έχει πλάτος w + 1 εικονοστοιχείο και ύψος h + 1 εικονοστοιχείο. Το μικρότερο δυνατό τετράγωνο, αποτελούμενο από 2 x 2 εικονοστοιχεία, μπορεί να σχεδιαστεί με αυτή την δήλωση g.drawrect(x, y, 1, 1); Για να βάλουμε μόνο ένα εικονοστοιχείο στην οθόνη, δεν μπορούμε να χρησιμοποιήσουμε την drawrect, επειδή τίποτα απολύτως δεν θα εμφανιστεί εάν προσπαθήσουμε να ρυθμίσουμε το τρίτο και το τέταρτο όρισμα αυτής της μεθόδου στο μηδέν. Περιέργως όλως, η Java δεν μας δίνει κάποια ειδική μέθοδο για αυτόν τον σκοπό, οπότε πρέπει να χρησιμοποιήσουμε αυτήν την δήλωση 5
g.drawline(x, y, x, y); Σημειώστε ότι η δήλωση g.drawline(xa, y, xb, y); σχεδιάζει μια οριζόντια γραμμή αποτελούμενη από xb xa + 1 εικονοστοιχείο. Σχήμα 1.3: Μικρές γεμισμένες περιοχές. 1.2 ΤΑ ΟΡΙΑ ΤΩΝ ΓΕΜΙΣΜΕΝΩΝ ΠΕΡΙΟΧΩΝ Στα μαθηματικά, στην γραφική μας έξοδο οι γραμμές είναι συνεχόμενες και δεν έχουν πάχος αλλά είναι διακριτικές και τουλάχιστον τόσο παχιές όσο ένα εικονοστοιχείο. Αυτή η διαφορά στην ερμηνεία της ιδέας των γραμμών μπορεί να μην προκαλεί κανένα πρόβλημα εάν τα εικονοστοιχεία είναι πολύ μικρά συγκριτικά με αυτό που θέλουμε να σχεδιάσουμε. Παρόλαυτα, πρέπει να έχουμε υπόψιν μας ότι μπορεί να υπάρξει πρόβλημα σε κάποιες ειδικές περιπτώσεις, όπως φαίνεται στο Σχήμα 1.3 (α). Ας υποθέσουμε ότι έχουμε να σχεδιάσουμε ένα γεμισμένο τετράγωνο ΑΒCD σε διαστάσεις με 4 x 4 εικονοστοιχεία, αποτελούμενο από το κάτω δεξιά τρίγωνο ΑΒC και το πάνω αριστερά τρίγωνο ΑCD, το οποίο θέλουμε να χρωματίσουμε σκούρο γκρι και ανοιχτό γκρι αντίστοιχα, χωρίς να σχεδιάσουμε καμμία γραμμή. Περιέργως όλως, δεν είναι ξεκάθαρο πώς μπορεί να γίνει κάτι τέτοιο: αν κάνουμε την διαγώνιο ΑC ανοιχτή γκρι, το τρίγωνο ΑΒC περιέχει λιγότερα εικονοστοιχεία από το τρίγωνο ΑCD. Αν το κάνουμε σκούρο γκρι θα συμβεί το αντίθετο. Ένα πολύ ευκολότερο πρόβλημα, όπως φαίνεται στο Σχήμα 1.3 (β), είναι να γεμίσουμε για παράδειγμα τα τετράγωνα μιας σκακιέρας, με σκούρα και ανοιχτά γκρι τετράγωνα αντί για άσπρα και μαύρα. Αντίθετα με το τι συμβαίνει με τα τετράγωνα στα μαθηματικά, αυτά που εμφανίζονται σε μια οθόνη υπολογιστή χρειάζονται ειδική προσοχή σχετικά με το αν οι πλευρές ανήκουν ή όχι στις γεμισμένες περιοχές. Έχουμε δει ότι η δήλωση g.drawrect(x, y, w, h); σχεδιάζει ένα ορθογώνιο με γωνίες (x, y) (x + w, y + h). Η μέθοδος fillrect, από την άλλη μεριά, γεμίζει ένα ελαφρώς μικρότερο ορθογώνιο. Η δήλωση g.fillrect(x, y, w, h); ορίζει το τρέχον χρώμα του προσκηνίου σε ένα ορθογώνιο αποτελούμενο από w x h εικονοστοιχεία. Αυτό το ορθογώνιο έχει (x, y) ως την πάνω αριστερή γωνία του και (x + w -1, y + h -1) ως την κάτω δεξιά γωνία του. Για να αποκτήσουμε μια γενικοποίηση του Σχήματος 1.3 (β), η ακόλουθη μέθοδος, checker, σχεδιάζει μία n x n σκακιέρα, με (x, y) ως την πάνω αριστερή γωνία 6
της και με σκούρα γκρι και ανοιχτά γκρι τετράγωνα, το καθένα αποτελούμενο απο w x w εικονοστοιχεία. Το κάτω αριστερά τετράγωνο πάντα θα είναι σκούρο γκρι, γιατί γι αυτό το τετράγωνο έχουμε i = 0 και j = n - 1, έτσι ώστε i + n = 1: void checker(graphics g, int x, int y, int n, int w) { for (int i=0; i<n; i++) for (int j=0; j<n; j++) { g.setcolor((i + n - j) % 2 == 0? Color.lightGray : Color.darkGray); g.fillrect(x + i * w, y + j * w, w, w); Εάν θέλαμε να σχεδιάσουμε μόνο τις πλευρές του κάθε τετραγώνου, επίσης σε ανοιχτό και σκούρο γκρι, θα έπρεπε να αντικαταστήσουμε την παραπάνω δήλωση στην fillrect με g.drawrect(x + i * w, y + j * w, w - 1, w - 1); στην οποία δήλωση τα τελευταία 2 ορίσματα είναι w - 1 αντί για w. 1.3 Λ Ο Γ Ι Κ Ε Σ Σ Υ Ν Τ Ε Τ Α Γ Μ Ε Ν Ε Σ 1.3.1 Η κατεύθυνση του άξονα Υ Όπως βλέπουμε στο Σχήμα 1.2, η προέλευση των συστημάτων συντεταγμένων συσκευής είναι η πάνω αριστερή γωνία του καμβά, έτσι ώστε ο θετικός άξονας Y να δείχνει προς τα κάτω. Λογικό για έξοδο κειμένου, μιας και ξεκινάμε από την κορυφή και αυξάνουμε το y καθώς συνεχίζουμε με την επόμενη γραμμή κειμένου. Παρολαυτά, αυτή η κατεύθυνση του άξονα y είναι διαφορετική από την συνηθισμένη πρακτική στα μαθηματικά και επομένως συχνά δεν βολεύει για εφαρμογές γραφικών. Για παράδειγμα, σε μια συζήτηση για μία γραμμή με θετική κλίση, περιμένουμε πηγαίνουμε προς τα πάνω όταν μετακινούμαστε κατά μήκος της γραμμής από αριστερά προς τα δεξιά. Ευτυχώς, μπορούμε να το ρυθμίσουμε έτσι ώστε η θετική κατεύθυνση του y να αντιστραφεί εκτελώντας αυτή την απλή μετατροπή: y = maxy - y 1.3.2 Συνεχείς και Διακριτές Συντεταγμένες Αντί για τις διακριτές (ακέραιες) συντεταγμένες που χρησιμοποιούμε σε ένα χαμηλότερο επίπεδο, προσανατολισμένο προς την συσκευή, θέλουμε να χρησιμοποιήσουμε συνεχόμενες συντεταγμένες κινητής υποδιαστολής σε ένα υψηλότερο επίπεδο, προσανατολισμένο προς το πρόβλημα. Οι συνηθισμένοι όροι είναι συντεταγμένες συσκευής και λογικές συντεταγμένες αντίστοιχα. Το να υπολογίσουμε συντεταγμένες συσκευής για τις αντίστοιχες λογικές συντεταγμένες και το αντίστροφο, είναι λιγάκι περίπλοκο. Πρέπει να έχουμε υπόψιν μας ότι υπάρχουν δύο λύσεις σε αυτό το πρόβλημα, ακόμα και στην απλή 7
περίπτωση στην οποία αυξάνοντας μία λογική συντεταγμένη κατά ένα, έχει ως αποτέλεσμα την αύξηση της συντεταγμένης συσκευής, επίσης κατά ένα. Θέλουμε να γράψουμε τις ακόλουθες μεθόδους: ix (x), iy (y): fx (x), fy (y): οι συντεταγμένες συσκευής του σημείου με τις λογικές συντεταγμένες χ και y οι λογικές συντεταγμένες του σημείου με τις συντεταγμένες συσκεύης χ και y Σχετικά με τις συντεταγμένες χ, η πρώτη λύση βασίζεται στην στρογγυλοποίηση: int ix(float x) {return Math.round(x); float fx(int x){return (float)x; Για παράδειγμα, με αυτή την λύση έχουμε ix(2.8) = 3 and fx(3) = 3.0 Η δεύτερη λύση βασίζεται στην περικοπή: int ix(float x) {return (int )x; float fx(int x){return (float)x + 0.5F; //Not used in //this book Με αυτές τις λειτουργίες μετατροπής, θα έχουμε ix(2.8) = 2 and fx(2) = 2.5 Και με τις δύο λύσεις, η διαφορά μεταξύ κάθε τιμής x και fx (ix(x)) δεν είναι μεγαλύτερη από 0.5. θα χρησιμοποιήσουμε την πρώτη λύση σε όλο το βιβλίο, μιας και είναι η καλύτερη αν οι λογικές συντεταγμένες συχνά τυχαίνει να είναι ακέραιοι αριθμοί. Σε αυτές τις περιπτώσεις το να περικόπτουμε αριθμούς κινητής υποδιαστολής συχνά οδηγεί σε χειρότερα αποτελέσματα απ ότι θα είχαμε με την στρογγυλοποίηση. Εκτός από τις παραπάνω μεθόδους ix και fx (βασιζόμενοι στην πρώτη λύση) για τις συντεταγμένες x, χρειαζόμαστε παρόμοιες μεθόδους για τις συντεταγμένες y, λαμβάνοντας υπόψιν μας τις αντίθετες κατευθύνσεις των δύο αξόνων y. Στο κάτω μέρος του καμβά η συντεταγμένη συσκευής y είναι maxy ενώ η λογική συντεταγμένη y είναι 0, το οποίο μπορεί να εξηγεί τις δύο εκφράσεις της μορφής maxy -... στις ακόλουθες μαθόδους: int ix(float x) {return Math.round(x); int iy(float y){return maxy - Math.round(y); float fx(int x){return (float)x; float fy(int y){return (float)(maxy - y); Το Σχήμα 1.4 δείχνει ένα μέρος του καμβά, βασισμένο στο maxy = 16. 8
public class Triangles extends Frame { public static void main(string[] args){new Triangles(); Triangles() { super("triangles: 50 triangles inside each other"); addwindowlistener(new WindowAdapter() {public void windowclosing(windowevent e){system.exit(0);); setsize (600, 400); add("center", new CvTriangles()); show(); class CvTriangles extends Canvas { int maxx, maxy, minmaxxy, xcenter, ycenter; void initgr() { Dimension d = getsize(); maxx = d.width - 1; maxy = d.height - 1; minmaxxy = Math.min(,maxX, maxy); xcenter = maxx/2; ycenter = maxy/2; int ix(float x){return Math.round(x); int iy(float y){return maxy - Math.round(y); public void paint(graphics g) { initrg(); float side = 0.95F * minmaxxy, sidehalf = 0.5F * side, h = sidehalf * (float)math.sqrt (3). xa, ya, xb, yb, xc, yc, xa1, ya1, xb1, yb1, xc1, yc1, p, q; q = 0.05F; p = 1 - q; xa = xcenter - sidehalf; ya = ycenter - 0.05F * h; xb = xcenter + sidehalf; yb = ya; xc = xcenter; yc = ycenter + 0.05F * h; for (int i=0; i<50; i++) { g.drawline(ix(xa), iy(ya), ix(xb), iy(yb)); g.drawline(ix(xb), iy(yb), ix(xc), iy(yc)); g.drawline(ix(xc), iy(yc), ix(xa), iy(ya)); xa1 = p * xa + q * xb; ya1 = p * ya + q * yb; xb1 = p * xb + q * xc; yb1 = p * yb + q * yc; xc1 = p * xc + q * xa; yc1 = p * yc + q * ya; xa = xa1; xb = xb1; xc = xc1; ya = ya1; yb = yb1; yc = yc1; 9
Στην κλάση του καμβά CvTriangles υπάρχει μια μέθοδος initgr. Μαζί με τις άλλες γραμμές προγράμματος που προηγούνται της μεθόδου paint σε αυτήν την κλάση, η μέθοδος initgr μπορεί επίσης να είναι χρήσιμη σε άλλα προγράμματα. Είναι σημαντικό να προσέξουμε ότι σε κάθε πλευρά του τριγώνου, για περαιτέρω υπολογισμούς χρησιμοποιούνται οι υπολογισμένες συντεταγμένες κινητής υποδιαστολής και όχι οι ακέραιες συντεταγμένες συσκευής που προήλθαν από αυτές. το οποίο έρχεται σε αντίθεση με την ακόλουθη απεικόνιση, την οποία καλό θα ήταν να την αποφεύγουμε. Εδώ οι συντεταγμένες συσκευής int που περιέχουν στρογγυλοποιημένα λάθη χρησιμοποιούνται όχι μόνο για εξόδους γραφικών αλλά και για περαιτέρω υπολογισμούς, έτσι ώστε τέτοια λάθη να συσσωρεύονατι. Περιληπτικά, συκρίνουμε τα συστήματα λογικών συντεταγμένων και συντεταγμένων συσκυευής στον ακόλουθο πίνακα, όσον αφορά (1) τους κανόνες που χρησιμοποιούνται στο κέιμενο, αλλά όχι και στα προγράμματα Java, αυτού του βιβλίου (2) τους τύπους δεδομένων της προγραμματιστική γλώσσας (3) το πεδίο της τιμής της συντεταγμένης (4) την κατεύθυνση του άξονα y: σύστημα συντεταγμένων Σύμβαση Τύπος δεδομένων Πεδίο τιμών Θετικός άξονας y Λογικό Μικρά γράμματα Κινητής Συνεχές Ανοδικός υποδιαστολής Συσκευής Κεφαλαία γράμματα Ακέραια Διακριτό Καθοδικός 10
1.4 Α Ν Ι Σ Ο Τ Ρ Ο Ι Κ Α Ι Ι Σ Ο Τ Ρ Ο Π Ι Κ Ο Ι Μ Ε Θ Ο Δ Ο Ι Χ Α Ρ Τ Ο Γ Ρ Α Φ Η Σ Η Σ 1.4.1 Χαρτογραφόντας ένα συνεχές διάστημα σε μια ακολουθία ακέραιων αριθμών Ας υποθέσουμε ότι θέλουμε να χαρτογραφήσουμε ένα διάστημα αληθινών λογικών συντεταγμένων, όπως 0 χ 10.0 στο σύνολο των ακέραιων αριθμών συσκευής (0, 1, 2,..., 9). Δυστυχώς, η μέθοδος int ix(float x){return Math.round(x); που χρησιμοποιήθηκε στην προηγούμενη ενότητα, δεν είναι κατάλληλη γι αυτό τον σκοπό επειδή για κάθε χ μεγαλύτερο από 9.5 (και όχι μεγαλύτερο από 10) μας δίνει 10, που δεν ανήκει στην επιτρεπόμενη ακολουθία 0, 1,..., 9. Συγκεκριμένα μας δίνει ix (10.0) = 10 ενώ εμείς θέλουμε ix (10.0) = 9 Αυτό υποδηλώνει ότι σε μία βελτιωμένη μέθοδο ix θα πρέπει να χρησιμοποιήσουμε έναν πολλαπλασιαστικό παράγοντα της τάξης του 0.9. Μπορούμε επίσης να καταλήξουμε σε αυτό το συμπέρασμα συνηδητοποιώντας ότι υπάρχουν μόνο εννέα μικρά διαστήματα μεταξύ των δέκα εικονοστοιχείων ονομαζόμενα 0, 1,..., 9, όπως μας δείχνει το Σχήμα 1.6. Εάν ορίσουμε το πλάτος του εικονοστοιχείου (= pixelwidth) ως την απόσταση μεταξύ δύο συνεχόμενων εικονοστοιχείων σε μία οριζόντια γραμμή, το πιο πάνω διάστημα 0 x 10 των λογικών συντεταγμένων (ώντας αληθινοί αριθμοί) αντιστοιχεί σε 9 x pixelwidth. Έτσι σε αυτό το παράδειγμα έχουμε: 9 x pixelwidth = 10.0 (το μήκος του διαστήματος των λογικών συντεταγμένων) pixelwidth = 10/9 = 1.111... Γενικότερα, εάν μια οριζόντια γραμμή του παραθύρου μας αποτελείται από n εικονοστοιχεία, αριθμημένα ως 0, 1,..., maxx (όπου maxx = n -1), και το αντίστοιχο (συνεχές) Σχήμα 1.6: Εικονοστοιχεία που βρίσκονται 10/9 λογικές μονάδες μεταξύ τους διάστημα των λογικών συντεταγμένων είναι 0 χ rwidth, μπορούμε να χρησιμοποιήσουμε την ακόλουθη μέθοδο: 11
int ix(float x){return Math.round(x/pixelWidth); όπου το pixelwidth υπολογίζεται απο πριν ως εξής: maxx = n 1; pixelwidth = rwidth/maxx; Στο παραπάνω παράδειγμα ο ακέραιος n είναι ίσος με το μήκος του διαστήματος rwidth, αλλά είναι συχνά επιθυμητό να χρησιμοποιούμε τις λογικές συντεταγμένες x και y ικανοποιώντας 0 χ rwidth 0 y rheight όπου rwidth και rheight είναι πραγματικοί αριθμοί, όπως 10.0 και 7.5 αντίστοιχα, οι οποίοι είναι εντελώς διαφορετικοί από τους αριθμούς των εικονοστοιχείων που βρίσκονται σε οριζόντιες και κάθετες γραμμές. Θα είναι σημαντικό να διαχωρίσουμε μεταξύ μιας ισοτροπικής και μιας ανισότροπης μεθόδου χαρτογράφησης, όπως θα συζητήσουμε σε λίγο. Όσο για την πιο απλή μέθοδο int ix(float x){return Math.round(x); της προηγούμενης ενότητας, μπορεί να θεωρηθεί ως μια ειδική περίπτωση της βελτιωμένης μεθόδου που μόλις είδαμε, δεδομένου ότι χρησιμοποιούμε rwidth = 1, δηλαδή rwidth = maxx ή rwidth = n - 1. Για παράδειγμα, εάν το ορθογώνιο σχεδίασης είναι 100 εικονοστοιχεία σε πλάτος, έτσι ώστε n = 100 και μπορούμε να χρησιμοποιήσουμε τα εικονοστοιχεία 0, 1, 2,..., 99 = maxx σε μια οριζόντια γραμμή, αυτή η πιό απλή μέθοδος ix λειτουργεί σωστά εάν εφαρμοστεί σε λογικές συντεταγμένες χ, ικανοποιώντας 0 χ rwidth = 99.0 Αυτό που πρέπει να προσέξουμε έιναι ότι λόγω η τιμή του pixelwidth = 1, το λογικό πλάτος είναι 99.0 αν και ο αριθμός των διαθέσιμων εικονοστοιχείων είναι 100. 1.4.2 Ανιστοτροπική Μέθοδος Χαρτογράφησης Ο όρος ανισοτροπική μέθοδος χαρτογράφησης υποδηλώνει ότι οι συντελεστές κλίμακας για το χ και το y δεν είναι απαραίτητα ίσοι, όπως μας δείχνει και το ακόλουθο κομμάτι κώδικα Dimension d = getsize(); maxx = d.width - 1; maxy = d.height - 1; pixelwindth = rwidth/maxx; pixelheight = rheight/maxy;... int ix(float x){return Math.round(x/pixelWidth); int iy(float y){return maxy - Math.round(y/pixelHeight); float fx(int x){return x * pixelwindth; float fy(int y){return (maxy - y) * pixelheight; 12
Ας το χρησιμοποιήσουμε αυτό σε ένα πρόγραμμα επίδειξης. Άσχετα με τις διαστάσεις του παραθύρου, το μεγαλύτερο δυνατό ορθογώνιο έχει τις λογικές διαστάσεις 10.0 x 7.5. Κάνοντας κλικ σε ένα σημείο του καμβά, εμφανίζονται οι λογικές συντεταγμένες, όπως μας δείχνει το Σχήμα 1.7. Σχήμα 1.7: Λογικές συντεταγμένες με την ανισοτροπική μέθοδο χαρτογράφησης Μιας και δεν υπάρχουν καθόλου κενά μεταξύ αυτού του ορθογωνίου και τις πλευρές του παραθύρου, μπορούμε να δούμε το ορθογώνιο με κάποια δυσκολία στο Σχήμα 1.7. Αντιθέτως, η οθόνη θα δείξει αυτό το ορθογώνιο πολύ καθαρά επειδή θα το κάνουμε κόκκινο αντί για μαύρο. Αν και οι διαστάσεις του παραθύρου στο Σχήμα 1.7 έχουν αλλαχθεί από τον χρήστη, οι λογικές διαστάσεις του καμβά είναι 10.0 x 7.5. Το κείμενο που φαίνεται στο παράθυρο δείχνει τις συντεταγμένες του σημείου κοντά στην πάνω δεξιά γωνία του ορθογωνίου, όπως μας δείχνει ο δρομέας. Εάν ο χρήστης κάνει κλικ ακριβώς σε εκείνη την γωνία, οι συντεταγμένες 10.0 και 7.5 εμφανίζονται. Το συγκεκριμένο πρόγραμμα επίδειξης συντάσσεται παρακάτω //Anisotr.java:The anisotropic mapping mode. import java.awt.*; import java.awt.event*; public class Anisotr extends Frame { public static void main(string[] args){new Anisotr(); Anisotr() { super("anisotropic mapping mode"); addwindowlistener(new WindowAdapter() { public void windowclosing(windowevent e) {System.exit(0);); setsize (400, 300); add("center", new CvAnisotr()); setcursor(cursor.getpredefinedcursor(cursor.crosshair_cursor)); show(); class CvAnisotr extends Canvas { int maxx, maxy; float pixelwidth, pixelheight, rwidth = 10.0F, rheight = 7.5F, xp = -1, yp; CvAnisort() { addmouselistener(new MouseAdapter() { public void mousepressed(mouseevent evt) { xp = fx(evt.getx());yp = fy(evt.gety()); repaint(); ); void initgr() 13
{ Dimension d = getsize(); maxx = d.width -1; maxy = d.height - 1; pixelwidth = rwidth/maxx; pixelheight = rheight/maxy; int ix(float x) {return Math.round(x/pixelWidth); int iy(float y){return maxy - Math.round(y/pixelHeight); float fx(int x){return x * pixelwidth; float fy(int y){return (maxy - y) * pixelheight; public void paint(graphics g) { initgr(); int left = ix (0), right = ix(rwidth), bottom = iy (0), top = iy(rheight); if (xp >= 0) g.drawstring( "Logical coordinates of selected point: " + xp + " " + yp, 20, 100); g.setcolor(color.red); g.drawline(left, bottom, right, bottom); g.drawline(right, bottom, right, top); g.drawline(right, top, right, top); g.drawline(left, top, left, bottom); Με την ανισοτροπική μέθοδο χαρτογράφησης, το πραγματικό μήκος μιας κάθετης μονάδας μπορεί να είναι διαφορετικό από το μήκος μιας οριζόντιας μονάδας. Αυτό ισχύει και για το Σχήμα 1.7: αν και το ορθογώνιο είναι 10 μονάδες σε πλάτος και 7.5 σε ύψος, το πραγματικό του μέγεθος είναι μικρότερο από 0.75 του πλάτους του. Πιο συγκεκριμένα, η ανισοτροπική μέθοδος χαρτογράφησης δεν είναι κατάλληλη για την σχεδίαση τετραγώνων, κύκλων και άλλων σχημάτων που απαιτούνται ίσες μονάδες στις οριζόντιες και τις κάθετες κατευθύνσεις. 1.4.3 Ισοτροπική Μέθοδος Χαρτογράφησης Μπορούμε να κάνουμε τις οριζόντιες και τις κάθετες μονάδες να είναι ίσες, όσον αφορά το πραγματικό τους μέγεθος, χρησιμοποιώντας τον ίδιο συντελεστή κλίμακας για το x και για το y. Ας χρησιμοποιήσουμε τον όρο ορθογώνιο σχεδίασης για το ορθογώνιο με διαστάσεις rwidth και rheight, στις οποίες συνήθως σχεδιάζουμε γραφικές εξόδους. Μιας και αυτές οι λογικές διαστάσεις είναι σταθερές, σταθερή είναι και η αναλογία τους, κάτι που δεν ισχύει με την αναλογία των διαστάσεων του καμβά. Έτσι, με την ισοτροπική μέθοδο χαρτογράφησης, το ορθογώνιο σχεδίασης γενικά, δεν θα είναι όμοιο με τον καμβά. Ανάλογα με το μέγεθος του τρέχοντος παραθύρου, είτε οι πάνω και οι κάτω, είτε οι αριστερές και οι δεξιές πλευρές του ορθογώνιου σχεδίασης βρίσκονται σε αυτές του καμβά. Μιας και συνήθως είναι επιθυμητό για ένα σχέδιο να εμφανίζεται στο κέντρο του καμβά, βολεύει με την ισοτροπική μέθοδο χαρτογράφησης να τοποθετήσουμε την προέλευση του συστήματος λογικών συντεταγμένων σε αυτό το κέντρο. Αυτό υποδηλώνει ότι θα χρησιμοποιήσουμε τα ακόλουθα διαστήματα λογικών συντεταγμένων. -1/2rWidth χ +1/2 rwidth 14
-1/2rHeight y +1/2rHeight Οι μέθοδοι μας ix και iy θα χαρτογραφήσουν το κάθε ζεύγος λογικών συντεταγμένων (x, y) με ένα ζεύγος (X, Y) συντεταγμένων συσκευής, όπου X є {0,1,2,...maxX Y є {0,1,2,...maxY Για να επιτύχουμε τον ίδιο συντελεστή μονάδας για το x και για το y, υπολογίζουμε rwidth / maxx και rheight / maxy και παίρνουμε την μεγαλύτερη από αυτές τις δύο τιμές. Αυτή την ανώτατη τιμή, pixelsize, την χρησιμοποιούμε μετά στις μεθόδους ix και iy, όπως φαίνεται και από το παρακάτω απόσπασμα Dimension d = getsize(); int maxx = d.width - 1; maxy = d.height - 1; pixelsize = Math.max(rWidth/maxX, rheight/maxy); centerx = maxx/2; centery = maxy/2;... int ix(float x){return Math.round(center + x/pixelsize); int iy(float y){return Math.round(centerY - y/pixelsize); float fx(int x){return (x centerx) * pixelsize; float fy(int y){return (center - y) * pixelsize; Θα χρησιμοποιήσουμε αυτόν τον κώδικα σε ένα πρόγραμμα που σχηματίζει ένα τετράγωνο, 2 γωνίες του οποίου αγγίζουν είτε τα σημεία που βρίσκονται στην μέση των οριζόντιων πλευρών του καμβά ή των καθέτων. Επίσης μας εμφανίζει τις συντεταγμένες του σημείου πάνω στο οποίο κάνει κλικ ο χρήστης, όπως μας δείχνει το αριστερό παράθυρο του Σχήματος 1.8. Σχήμα 1.8. Παράθυρα μετά την αλλαγή του μεγέθους τους Σε αυτή την απεικόνιση, πρέπει να δώσουμε ιδιαίτερη προσοχή στις γωνίες του σχεδιασμένου τετραγώνου που αγγίζουν τα όρια του ορθογωνίου σχεδίασης. Αυτές οι γωνίες δεν βρίσκονται στο πλαίσιο του παραθύρου, αλλά από μέσα του. Για το τετράγωνο στα αριστερά έχουμε: Σχήμα 1.8, αριστερά λογικές συντεταγμένες y λογική συντεταγμένη iy (y) πάνω γωνία +rheight/2 0 κάτω γωνία -rheight/2 maxy 15
Σε αντίθεση, για το τετράγωνο που έχει σχεδιαστεί στο στενό παράθυρο στα δεξιά, είναι οι γωνίες στα αριστερά και στα δεξιά που βρίσκονται ακριβώς μέσα στο πλαίσιο. Σχήμα 1.8, δεξιά λογικές συντεταγμένες x λογική συντεταγμένη ix (x) πάνω γωνία -rwidth/2 0 κάτω γωνία -rwidth/2 maxx //Isotrop.java:THe isortopic mapping mode //origin og logical coordinate system in canvas //center; positive y-axis upward //Square (turned 45 degrees) just fits into canvas //Mouse click displays logical coordinates of selected point. import java.awt.*; import java.awt.event*; public class Isotrop extends Frame { public static void main(string[] args){new Isotrop(); Isotrop() { super("isotropic mapping mode"); addwindowlistener(new WindowAdapter() { public void windowclosing(windowevent e) {System.exit(0);); setsize (400, 300); add("center", new CvIsotrop()); setcursor(cursor.getpredefinedcursor(cursor.crosshair_cursor)); show(); class CvIsotrop extends Canvas { int centerx, centery; float pixelsize, rwidth = 10.0F, rheight = 10.0F, xp = 1000000, yp; CvIsotrop() { addmouselistener(new MouseAdapter() { public void mousepressed(mouseevent evt) { xp = fx(evt.getx());yp = fy(evt.gety()); repaint(); ); void initgr() { Dimension d = getsize(); int maxx = d.width -1; maxy = d.height - 1; pixelsize = Math.max(rWidth/maxX; rheight/maxy); center = maxx/2; centery = maxy/2; int ix(float x) {return Math.round(centerX + x/pixelsize); int iy(float y){return Math.round(centerY - y/pixelsize); float fx(int x){return (x - centerx) * pixelsize; float fy(int y){return (center - y) * pixelsize; public void paint(graphics g) { initgr(); 16
int left = ix (-rwidth/2), right = ix(rwidth/2), bottom = iy (-rheight/2), top = iy(rheight/2); xmiddle = ix (0), ymiddle = iy (0); g.drawline(xmiddle, bottom, right, ymiddle); g.drawline(right, ymiddle, xmiddle, top); g.drawline(xmiddle, top, left, ymiddle); g.drawline(left, ymiddle, xmiddle, bottom); if (xp!= 1000000) g.drawstring( "Logical coordinates of selected point:" + xp + " " + yp, 20, 100); 1.5 Ο Ρ Ι Ζ Ο Ν Τ Α Σ Ε Ν Α Π Ο Λ Υ Γ Ω Ν Ο Χ Ρ Η Σ Ι Μ Ο Π Ο Ι Ω Ν Τ Α Σ Τ Ο Π Ο Ν Τ Ι Κ Ι Θα χρησιμοποιήσουμε τις μεθόδους μετατροπής του προηγούμενου προγράμματος σε μία πιο ενδιαφέρουσα εφαρμογή, η οποία επιτρέπει στον χρήστη να ορίσει ένα πολύγωνο κάνοντας κλικ σε σημεία έτσι ώστε να καταδείξει τις θέσεις των κορυφών. Το Σχήμα 1.9 μας δείχνει ένα τέτοιο πολύγωνο, αφού ο χρήστης έχει ορίσει συνεχόμενες κορυφές, τελειώνοντας με την πρώτη στα αριστερά, η οποία είναι μαρκαρισμένη με ένα μικρό ορθογώνιο. Το μεγάλο ορθογώνιο που περιβάλλει το πολύγωνο είναι το ορθογώνιο σχεδίασης: μόνο οι κορυφές μέσα σε αυτό το ορθογώνιο θα εμφανιστούν εγγυημένα ξανά εάν ο χρήστης αλλάξει τις διαστάσεις του παραθύρου. Το πολύ πλατύ παράθυρο του Σχήματος 1.9 δημιουργήθηκε σέρνοντας μία από τις γωνίες του. Μετά από την σχεδίαση του πολυγώνου, οι διαστάσεις του παραθύρου άλλαξαν ξανά, για να επιτύχουμε το Σχήμα 1.10. Μπορούμε επίσης να αλλάξουμε τις διαστάσεις κατά την διάρκεια σχεδίασης του πολυγώνου. Ας κάνουμε μια περίληψη των απαιτήσεων αυτής της εφαρμογής. Σχήμα 1.9: Πολύγωνο ορισμένο από χρήστη Σχήμα 1.10: Πολύγωνο που παραμένει στο κέντρο του παραθύρου μετά από αλλαγή του μεγέθους του παραθύρου 17
Η πρώτη κορυφή σχεδιάζεται ως ένα μικρό ορθογώνιο Αν μία επόμενη κορυφή είναι μέσα στο μικρό ορθογώνιο, η σχεδίαση ενός πολυγώνου είναι ολοκληρωμένη. Στο ορθογώνιο σχεδίασης σχεδιάζονται μόνο κορυφές. Το ορθογώνιο σχεδίασης (βλ. Σχήμα 1.9 και 1.10) είναι είτε τόσο ψηλό, είτε τόσο φαρδύ όσο το παράθυρο, διατηρώντας όμως την αναλογία ύψους/πλάτους άσχετα με το σχήμα του παραθύρου. Όταν ο χρήστης αλλάξει το σχήμα του παραθύρου, το σχήμα του σχεδιασμένου πολυγώνου αλλάζει με τον ίδιο τρόπο όπως το ορθογώνιο σχεδίασης, όπως επίσης και ο λευκός χώρος που που περιβάλλει το πολύγωνο. Θα χρησιμοποιήσουμε την ισοτροπική μέθοδο χαρτογράφησης για να εφαρμόσουμε αυτό το πρόγραμμα, και θα χρησιμοποιήσουμε μία δομή δεδομένων που ονομάζεται διάνυσμα κορυφής για να αποθηκεύσουμε τις κορυφές του πολυγώνου που θα σχεδιαστεί. Εν συνεχεία σχεδιάζουμε το πρόγραμμα με τα ακόλουθα αλγοριθμικά βήματα: 1. Ενεργοποιούμε το ποντίκι 2. Όταν το αριστερό κουμπί του ποντικιού πατιέται 2.1 Παίρνουμε x- και y- συντεταγμένες εκεί όπου κάνουμ κλικ με το ποντίκι 2.2 Εάν είναι η πρώτη κορυφή Τότε αδειάζουμε το διάνυσμα κορυφής Αλλιώς, εάν η κορυφή είναι μέσα στο μικρό τετράγωνο Τότε τελειώνουμε το τρέχων πολύγωνο Αλλιώς αποθηκεύουμε την κορυφή στο διάνυσμα κορυφής (δηλ. όχι στην τελευταία κορυφή 3. Σχεδιάζουμε όλες τις κορυφές στο διάνυσμα κορυφής. Το τελευταίο βήμα στον σχεδιασμό όλων των κορυφών του πολυγώνου είναι το ακόλουθο: 1. Βρίσκουμε τις διαστάσεις του σχεδιαστικού πολυγώνου βασιζόμενοι σε λογικές συντεταγμένες 2. Σχεδιάζουμε το ορθογώνιο σχεδίασης 3. Παίρνουμε την πρώτη κορυφύ από το διάνυσμα κορυφής 4. Σχεδιάζουμε ένα μικρό ορθογώνιο σην περιοχή της κορυφής 5. Σχεδιάζουμε μια γραμμή για κάθε δύο συνεχόμενες κορυφές που είναι αποθηκευμένες στο διάνυσμα κορυφής Και στο Σχήμα 1.9 αλλά και στο Σχήμα 1.10, το ορθογώνιο σχεδίασης έχει πλάτος 10 και ύψος 7.5 λογικές μονάδες, όπως μας δείχνει το ακόλουθο πρόγραμμα: //Defpoly.java: Drawing a polygon //Uses: CvDefpoly (discussed below). import java.awt.*; import java.awt.event*; public class Defpoly extends Frame { public static void main(string[] args){new Defpoly(); Defpoly() 18
{ super("defpoly polygon vertices by clicking"); addwindowlistener(new WindowAdapter() { public void windowclosing(windowevent e) {System.exit(0);); setsize (500, 300); add("center", new CvDefpoly()); setcursor(cursor.getpredefinedcursor(cursor.crosshair_cursor)); show(); Η κλάση CvDefPoly που χρησιμοποιήθηκε σε αυτό το πρόγραμμα, δίνεται παρακάτω. Ορίζουμε αυτή την κλάση σε ένα ξεχωριστό αρχείο, CvDefPoly.java, έτσι ώστε να είναι ευκολότερο να την χρησιμοποιήσουμε κάπου αλλού, όπως θα δούμε στην ενότητα 2.13: //CvDefPoly.java: To de used in other program files //A class that enables the user to define //Uses:Point2D (discussed bellow) import java.awt.*; import java.awt.event*; import java.util.*; class CvDefPoly extends Canvas { Vector v = new Vector(); float x0, y0, rwidth = 10.0F, rheight = 7.5F, pixelsize; boolean ready = true; int centerx, centery; CvDefPoly() { addmouselistener(new MouseAdapter() { public void mousepressed(mouseevent evt) { float xa = fx(evt.get()), ya = fy(evt.gety()); if (ready) { v.removeallelements(); x0 = xa; y0 = ya; ready = false; float dx = xa - x0, dy = ya - y0; if (v.size() > 0 && dx * dx + dy * dy < 4 * pixelsize * pixelsize) ready = true; else v.addelement(new Point2D(xA, ya)); repaint(); ); void initgr() { Dimension d = getsize(); int maxx = d.width -1; maxy = d.height - 1; pixelsize = Math.max(rWidth/maxX; rheight/maxy); center = maxx/2; centery = maxy/2; int ix(float x) {return Math.round(centerX + x/pixelsize); int iy(float y){return Math.round(centerY - y/pixelsize); float fx(int x){return (x - centerx) * pixelsize; float fy(int y){return (center - y) * pixelsize; public void paint(graphics g) { initgr(); int left = ix (-rwidth/2), right = ix(rwidth/2), 19
bottom = iy (-rheight/2), top = iy(rheight/2); g.drawrect(left, top, right - left, bottom - top); int n = v.size(); if (n == 0) return; point2d a= (point2d)(v.elementat (0) ); //show tiny rectangle around first vertex: g.drawrect (ix(a.x)-2, iy(a.y)-2, 4, 4); for (int i=1; i<=n; i++) { if (i == n &&! ready) break; point2d d = (point2d) (v.elementat(i % n)); g.drawline(ix(a.x), iy(a.y), ix(b.x), iy(b.y)); a = b; Η κλάση Point2D, που χρησιμοποιήθηκε στο παραπάνω αρχείο, θα φανεί επίσης χρήσιμη σε άλλα προγράμματα, οπότε την ορίζουμε και αυτήν σε ένα ξεχωριστό αρχείο Point2D.java: //Point2D.java:Class for points in logical coordinates. class point2d { float x, y,; point2d(float x, float y){this.x = x; this.y = y; Αφού έχει εμφανιστεί ένα ολοκληρωμένο πολύγωνο (κάτι το οποίο γίνεται όταν ο χρήστης ξανακοιτάξει την πρώτη κορυφή), ο χρήστης έχει ξανά την δυνατότητα να κάνει κλικ σε κάποιο σημείο. Το πολύγωνο τότε εξαφανίζεται και αυτό το σημείο θα είναι η πρώτη κορυφή ενός νέου πολύγωνου. Έχετε υπόψιν σας ότι η γραμμή εντολής // Uses: CvDefPoly που υπάρχει στο αρχείο DefPoly.java μας δείχνει ότι το αρχείο CvDefPoly.java θα πρέπει να υπάρχει στον τρέχοντα κατάλογο. Μιας και η γραμμή εντολής // Uses: Point2D συντελείται στο αρχείο CvDefPoly.java, το πρόγραμμα DefPoly.java επίσης απαιτεί την κλάση Point2D. Σχόλια όπως το παραπάνω είναι πολύ βοηθητικά εάν χρησιμοποιούνται διαφορετικοί κατάλογοι, όπως για παράδειγμα αν χρησιμοποιείται ένας για κάθε κεφάλαιο. 20
Α Σ Κ Η Σ Ε Ι Σ 1.1 Πόσα εικονοστοιχεία εμφανίζονται στην οθόνη με καθεμία από τις ακόλουθες δηλώσεις; g.drawline (10, 20, 100, 50) g.drawrect (10, 10, 8, 5) g.fillrect (10, 10, 8, 5) 1.2 Αντικαταστείστε τα τρίγωνα του προγράμματος Triangles.java με τετράγωνα και σχεδιάστε πολλά από αυτά, τακτοποιημένα σε μία σκακιέρα όπως δείχνει το Σχήμα 1.11. Ως συνήθως, η σκακιέρα αυτή αποτελείται από n x n κανονικά τετράγωνα (με οριζόντιες και κάθετες πλευρές), όπου n = 8. Το καθένα από αυτά στην πραγματικότητα αποτελείται από k τετράγωνα διαφόρων μεγεθών, με το k = 10. Τέλος, η τιμή q = 0.2 (και p = 1 - q = 0.8) χρησιμοποιήθηκε για να διαιρέσουμε κάθε πλευρά σε δύο μέρη με αναλογία p : q (δείτε επίσης το πρόγραμμα Triangles.java στην ενότητα 1.3), αλλά το ενδιαφέρον σχέδιο του Σχήματος 1.11 επετεύχθει αντιστρέφοντας τους ρόλους του p και του q στα μισά απο τα n x n κανονικά τετράγωνα που είναι παρόμοια με τα μαύρα και άσπρα τετράγωνα μιας κανονικής σκακιέρας. Το πρόγραμμας σας πρέπει να δεχθεί τις τιμές n, k και q ως ορίσματα. 1.3 Σχεδιάστε ένα σετ ομόκεντρων ζεύγων τετραγώνων, το καθένα αποτελούμενο από ένα τετράγωνο με οριζόντιες και κάθετες πλευρές και ένα περιστρεφόμενο κατά 45 μοίρες. Πέραν του πιο εξωτερικού τετραγώνου, οι κορυφές κάθε τετραγώνου να είναι τα κεντρικά σημεία των πλευρών του τετραγώνου που το περιβάλλει, όπως δείχνει το Σχήμα 1.12. Απαιτείται όλες οι γραμμές να είναι εντελώς ευθείες και οι κορυφές των μικρότερων τετραγώνω να βρίσκονται ακριβώς στις πλευρές των μεγαλύτερων. 21
Σχήμα 1.11: Μία σκακιέρα τετραγώνων Σχήμα 1.12: Ομόκεντρα τετράγωνα 22
Σχήμα 1.13: Εξάγωνα Σχήμα 1.14: Διακεκομμένες γραμμές 23
1.4 Γράψτε ένα πρόγραμμα το οποίο σχεδιάζει μία δομή εξαγώνων, όπως φαίνεται στο Σχήμα 1.13. Οι κορυφές ενός (κανονικού) εξαγώνου να βρίσκονται στο λεγόμενο οριοθετημένο κύκλο. Ο χρήστης πρέπει να έχει την ικανότητα να καθορίσει την ακτίνα του κύκλου κάνοντας κλικ σε ένα σημείο κοντά στην πάνω αριστερή γωνία ορθογωνίου σχεδίασης. Έπειτα, η απόσταση μεταξύ αυτού του σημείου και της γωνίας να χρησιμοποιηθεί ως ακτίνα του κύκλου που μόλις αναφέρθηκε. Πρέπει να υπάρχουν τόσα εξάγωνα με το καθορισμένο σχήμα όσα είναι δυνατόν και τα περιθώρια στα αριστερά και στα δεξιά πρέπει να είναι ίδια. Το ίδιο ισχύει και για τα πάνω και τα κάτω περιθώρια, όπως δείχνει και το Σχήμα 1.13. 1.5 Γράψτε μία κλάση Lines που να περιέχει μια στατική μέθοδο dashedline για να σχεδιάσετε διακεκομμένες γραμμές, με τέτοιο τρόπο ώστε να μπορούμε να γράψουμε Lines.dashedLine(g, xa, ya, xb, yb, dashlength) όπου g είναι η μεταβλητή του τύπου Graphics, xa, ya, xb, yb είναι οι συντεταγμένες συσκεύης των τελικών σημείων Α και Β και dashlength είναι το επιθυμητό μήκος (σε συντεταγμένες συσκευής) μίας παύλας. Πρέπει να υπάρχει μία παύλα και όχι κενό, σε κάθε τελικό σημείο μίας διακεκομμένης γραμμής. Το Σχήμα 1.14. δείχνει οχτώ διακεκομμένες γραμμές σχεδιασμένες με αυτόν τον τρόπο, με το dashlength = 20. 24
Κεφάλαιο 2 Εφαρμοσμένη Γεωμετρία Πριν συνεχίσουμε με συγκεκριμένα θέματα γραφικών υπολογιστή, θα συζητήσουμε και λίγα μαθηματικά, τα οποία θα χρησιμοποιούμε συχνά σε αυτό το βιβλίο. Μπορείτε να περάσετε τις τέσσερις πρώτες ενότητες αυτού του κεφαλαίου εάν είστε εξοικιωμένοι με διανύσματα και ορίζουσες. Μετά από αυτό το γενικό κομμάτι θα ασχοληθούμε με μερικούς χρήσιμους αλγόριθμους που θα χρειαστούν για τις ασκήσεις στο τέλος του κεφαλαίου και για τα θέματα στα υπόλοιπα κεφάλαια. Για παράδειγμα στα κεφάλαια 6 και 7 θα ασχοληθούμε με πολύγωνα που είναι όψεις 3D αντικειμένων. Μιας και τα πολύγωνα είναι γενικά δύσκολα, θα τα χωρίσουμε σε τρίγωνα όπως θα συζητήσουμε στην τελευταία ενότητα αυτού του κεφαλαίου. 25
2.1 Δ Ι Α Ν Υ Σ Μ Α Τ Α Αρχίζουμε με την μαθηματική έννοια του ορίσματος, που δεν πρέπει να συγχέεται με την κλάση Vector, που είναι διαθέσιμη στην Java για την αποθήκευση ενός αυθαίρετου αριθμού αντικειμένων. Θυμηθείτε ότι χρησιμοποιήσαμε αυτή την κλάση στην Ενότητα 1.5 για να αποθηκεύσουμε κορυφές πολυγώνων. Ένα διάνυσμα είναι ένα κατευθυνόμενο τμήμα γραμμής, που χαρακτηρίζεται μόνο από το μήκος του και την κατεύθυνση του. Το Σχήμα 2.1 μας δείχνει δύο αναπαραστάσεις του ίδιου διανύσματος a = PQ = b = RS. Έτσι, ένα διάνυσμα δεν τροποποιείται από μία μετάφραση. Το σύνολο c των διανυσμάτων a και b, γραμμένο c = a + b Σχήμα 2.1: Δύο ίσα διανύσματα μπορεί να επιτευχθεί σαν την διαγώνιο ενός παραλληλογράμμου, με το a, b και c να ξεκινάνε από το ίδιο σημείο, όπως μας δείχνει και το Σχήμα 2.2. Σχήμα 2.2: Πρόσθεση διανυσμάτων Το μήκος ενός διανύσματος a υποδηλώνεται με a. Ένα διάνυσμα με μηδενικό μήκος είναι το μηδενικό διάνυσμα και γράφεται ως 0. Το σύμβολο -a χρησιμοποιείται για το διάνυσμα που έχει μήκος a και η κατεύθυνση του είναι αντίθετη από αυτή του a. Για κάθε διάνυσμα a και πραγματικό αριθμό c, το διάνυσμα ca έχει μήκος c a. Εάν a = 0 ή c = 0, τότε ca = 0. Αλλιώς, το ca έχει την κατεύθυνση του a εάν c > 0 και την αντίθετη κατεύθυνση εάν c < 0. Για κάθε διάνυσμα u, v, w και πραγματικούς αριθμούς c, k, έχουμε u + v = v + u (u + v) + w = u + (v + w) u + 0 = u u + (-u) = 0 c(u + v) = cu + cv (c + k)u = (ck) + ku c(ku) = (ck)u 1u = u 0u = 0 26
Το Σχήμα 2.3 μας δείχνει τρία μοναδιαία διανύσματα i, j και k σε έναν τρισδιάστατο χώρο. Είναι αμοιβαία κατακόρυφα και έχουν μήκος 1. Οι κατευθύνσεις τους είναι οι θετικές κατευθύνσεις των αξόνων συντεταγμένων. Λέμε ότι το i, j και το k σχηματίζουν μια τρίαδα ορθογώνιων μοναδιαίων διανυσμάτων. Το σύστημα συντεταγμένων είναι δεξιόστροφο, το οποίο σημαίνει ότι αν μία περιστροφή του i προς την κατεύθυνση του j κατά 90 μοίρες αντιστοιχεί στο στρίψιμο μίας δεξιόστροφης σπείρας, τότε το k έχει την κατεύθυνση προς την οποία γυρνάει η σπείρα. Σχήμα 2.3: Δεξιόστροφο σύστημα συντεταγμένων Συχνά χρησιμοποιούμε την προέλευση Ο του συστήματος συντεταγμένων ως το αρχικό σημείο όλων των διανυσμάτων. Κάθε διάνυσμα v μπορεί να γραφτεί ως ένας γραμμικός συνδυασμός των μοναδιαίων διανυσμάτων i, j και k. v = xi + yi + zk Οι πραγματικοί αριθμοί x, y και z είναι οι συντεταγμένες του καταληκτικού σημείου P του διανύσματος v = OP. Συχνά γράφουμε αυτό το διάνυσμα v ως v = [ x y z ] or v = (x,y,z) Οι αριθμοί x, y, z κάποιες φορές ονομάζονται στοιχεία ή συστατικά του διανύσματος v. Η πιθανότητα να γράψουμε ένα διάνυσμα ως ακολουθία συντεταγμένων, όπως (x, y, z) για τρισδιάστατο χώρο, έχει οδηγήσει στην χρήση αυτού του όρου για ακολουθίες γενικότερα. Έτσι εξηγείται και η ονομασία Vector για μια κλάση της Java. Το μόνο που έχει κοινό η κλάση Vector με την μαθηματική έννοια του διανύσματος είναι ότι και τα δύο σχετίζονται με ακολουθίες. 27
2.2 Ε Σ Ω Τ Ε Ρ Ι Κ Ο Γ Ι Ν Ο Μ Ε Ν Ο Το εσωτερικό γινόμενο δύο διανυσμάτων a και b είναι ένας πραγματικός αριθμός, γραμμένος ως a b και ορίζεται ως a b = a b cos γ if a 0 and b 0 a b = 0 if a = 0 or b 0 (2.1) Όπου γ είναι η γωνία μεταξύ a και b. Ακολουθεί την πρώτη εξίσωση κατά την οποία a b είναι επίσης μηδέν αν γ = 90. Εφαρμόζοντας αυτόν τον ορισμό στα μοναδιαία διανύσματα i, j και k, βρίσκουμε i i = j j = k k = 1 i j = j i = j k = k j = k i = i k = 0 (2.2) Βάζοντας b = a στην Εξίσωση (2.1), έχουμε a a = a 2, έτσι a = a a Κάποιες σημαντικές ιδιότητες του εσωτερικού γινόμενου είναι c(ku v) = ck (u v) (cu + kv) w = cu w + kv w u v = v u u u = 0 only if u = 0 Το εσωτερικό γινόμενο των δύο διανυσμάτων u = [u 1 u 2 u 3 ] και v = [v 1 v 2 v 3 ] μπορεί να υπολογιστεί ως u v = u 1 v 1 + u 2 v 2 + u 3 v 3 Μπορούμε να το αποδείξουμε αυτό αναπτύσοντας το δεξί μέρος της ακόλουθης ισότητας ως το σύνολο εννέα εσωτερικών γινομένων και μετά εφαρμόζοντας την Εξίσωση (2.2): u v = (u 1 i + u 1 j + u 1 k) ( v 1 i + v 1 j + v 1 k) 2.3 Ο Ρ Ι Ζ Ο Υ Σ Ε Σ Πριν συνεχίσουμε με διανυσματικά γινόμενα, θα δώσουμε λίγο προσοχή στις ορίζουσες. Ας υποθέσουμε ότι θέλουμε να λύσουμε το ακόλουθο σύστημα δύο γραμμικών εξισώσεων για το x και y: (2.3) Μπορούμε έπειτα να πολλαπλασιάσουμε την πρώτη εξίσωση με b 2, την δεύτερη με b 1 και να τα προσθέσουμε, βρίσκοντας (a 1 b 2 a 2 b 1 )x = b 2 c 1 b 1 c 2 28
Μετά μπορούμε να πολλαπλασιάσουμε την πρώτη εξίσωση με -a 2 και την δεύτερη με a 1 και να κάνουμε ξανά την πρόσθεση, έχοντας (a 1 b 2 a 2 b 1 )y = a 1 c 2 a 2 c 1 Εάν το a 1 b 2 a 2 b 1 δεν είναι μηδέν, μπορούμε να διαιρέσουμε και να βρούμε Ο παρονομαστής σε αυτές τις εκφράσεις συχνά γράφεται με την μορφή και μετά ονομάζεται ορίζουσα. Επομένως Χρησιμοποιώντας ορίζουσες, μπορούμε να γράψουμε την λύση της Εξίσωσης (2.3) ως όπου Έχετε υπόψιν σας ότι το D i το βρίσκουμε από την αντικατάσταση της i-ιοστής στήλης του D με το δεξιό μέρος της Εξίσωσης (2.3) (i = 1 ή 2). Αυτή η μέθοδος για να λύσουμε ένα σύστημα γραμμικών εξισώσεων ονομάζεται Κανόνας του Κράμερ. Δεν περιορίζεται σε συστήματα δύο εξισώσεων (αν και θα ήταν πολύ ακριβό από άποψη υπολογιστικού χρόνου να εφαρμόσουμε αυτή την μέθοδο σε μεγάλα συστήματα). Οι ορίζουσες με n γραμμές και n στήλες είναι της νιοστής τάξεως. Ορίζουμε ως τις ορίζουσες της τρίτης τάξεως, χρησιμοποιώντας την εξίσωση όπου η κάθε υποορίζουσα M ij είναι η ορίζουσα 2 x 2 που παίρνουμε διαγράφοντας την i-ιοστή γραμμή και την j-ιοστή στήλη του D. Ορίζουσες υψηλότερων τάξεων ορίζονται με παρόμοιο τρόπο. Οι ορίζουσες είναι πολύ βοηθητικές στην γραμμική άλγεβρα και στην αναλυτική γεωμετρία. Έχουν πολλές ενδιαφέρουσες ιδιότητες κάποιες από τις οποίες παρατίθενται παρακάτω: Η τιμή μιας ορίζουσας παραμένει η ίδια εάν οι γραμμές της είναι γραμμένες ως στήλες στην ίδια τάξη. Για παράδειγμα: Αν οποιεσδήποτε δύο σειρές (ή στήλες) εναλλάσονται, η τιμή της ορίζουσας πολλαπλασιάζονται με - 1. Για παράδειγμα: 29
Αν οποιαδήποτε σειρά ή στήλη πολλαπλασιαστεί με κάποιον παράγοντα, η τιμή της ορίζουσας πολλαπλασιάζεται με αυτόν τον παράγοντα. Για παράδειγμα: Αν μια σειρά αλλαχτεί προσθέτοντας της οποιοδήποτε σταθερό πολλαπλασιαστή της οποιασδήποτε παραεπόμενης, η τιμή της ορίζουσας παραμένει ίδια. Το ίδιο μπορεί να εφαρμοστεί και στις στήλες. Για παράδειγμα: Εάν μια σειρά (ή μία στήλη) είναι ένα γραμμικός συνδυασμός άλλων γραμμών (ή στηλών), η τιμή της ορίζουσας είναι μηδέν. Για παράδειγμα: Σε πολλές περιπτώσεις, εξισώσεις ορίζουσων που εκφράζουν γεωμετρικές ιδιότητες είναι εύκολες να τις θυμόμαστε. Για παράδειγμα, η εξίσωση για την γραμμή στο R 2 μέχρι τα δύο σημεία P 1 (x 1, y 2 ) και P 2 (x 2, y 2 ) μπορεί να γραφτεί Κάτι τέτοιο μπορεί να γίνει κατανοητό παρατηρώντας, αρχικά, ότι η Eξίσωση (2.5) είναι μια ειδική σημείωση για μια γραμμική εξίσωση στο x και y, και ως αποτέλεσμα αντιπροσωπεύει μία ευθεία γραμμή στο R 2, και δεύτερον, ότι οι συντεταγμένες του P 1 και του P 2 ικανοποιούν αυτή την εξίσωση, γιατί αν τις γράψουμε στην πρώτη σειρά, θα έχουμε δύο πανομοιότυπες σειρές. Ομοίως, το επίπεδο στο R 3 μέχρι τα τρία σημεία P 1 (x 1, y 1, z 1 ), P 2 (x 2, y 2, z 2 ), P 3 (x 3, y 3, z 3 ) έχει την εξίσωση (2.5) που είναι πιο εύκολη να την θυμόμαστε από κάποια άλλη γραμμένη με τον συμβατικό τρόπο. (2.6) 30