ב-Online
 
 
 
 
 
 
 
לפרק את הבייט 
איך בונים תוכנה – חלק י' 

איך בונים תוכנה – חלק י'

 
לפרק את הבייט |
 
עידו גנדל

להפוך את דיאגרמות וורונוי לאלמנט גרפי זה עניין לא מסובך בכלל. עידו גנדל מפרק את הבייט, ומקנח בחידה

 
 
 
 
 
 
 
 
 
 
 
בטורים הקודמים אפיינו את התוכנה שלנו ויצרנו מסד נתונים לאלמנטים הגרפיים שיוצרים את דיאגרמות הוורונוי. זה היה נחמד וחשוב והכל, אבל משהו היה חסר: לא ראינו שום תוצאות צבעוניות בעיניים. הכל נשאר ברמה של קוד ושל טקסטים בקבצים. אלא שכאן מגיע החלק היפה בתכנות: אם בונים את היסודות כמו שצריך, התוצרים הסופיים באים בקלילות.

בחלק הקודם יצרנו את המחלקות "נקודה", "קו" ו"עיגול" וכתבנו את הקוד ששומר אותן לקובץ INI וטוען אותן ממנו. כעת, באותו אופן בדיוק, אנו יכולים לכתוב את הפונקציות שמציירות אותן. כדי לשמור את הפונקציות הללו כלליות ככל שניתן, בלי להתחייב לטופס תצוגה כזה או אחר, כל פונקציה תקבל כפרמטר את "לוח הציור" שעליו יש לצייר. בדלפי, מדובר במחלקה גרפית בסיסית בשם TCanvas. כך נראית הפונקציה של מחלקה מסוג "נקודה", שמציירת אותה על גבי TCanvas:
 


procedure TVoronoiPoint.Draw(const Canvas: TCanvas);
begin
Canvas.Pixels[X, Y] := Color;
end;



פשוט כך – המחלקה TCanvas מכילה מערך דו-ממדי בשם Pixels שבעזרתו אפשר לקבוע את הצבע של כל פיקסל בנפרד. במקרה זה, אנו מעניקים לו את הצבע של הנקודה הספציפית. במקרה של קו, זה קצת יותר מסובך – צריך לקבוע קודם כל את צבע ה"עט", אחר-כך לעבור למיקום של תחילת הקו, ומשם למתוח קו לסופו:


procedure TVoronoiLine.Draw(const Canvas: TCanvas);
begin
Canvas.Pen.Color := Color;
Canvas.MoveTo(X, Y);
Canvas.LineTo(X2, Y2);
end;



הפונקציה המתאימה עבור עיגול מורכבת עוד יותר: המחשב עלול לצייר עיגול מלא, ואנו מעוניינים בעיגול ריק. לכן, צריך להגדיר "מברשת מילוי" ריקה. כמו כן, הפקודה של TCanvas לציור אליפסות ועיגולים מסתמכת על המלבן התוחם, ואילו לנו יש נקודת מרכז ורדיוס, כך שאנחנו צריכים לבצע את החישוב לבד:


procedure TVoronoiCircle.Draw(const Canvas: TCanvas);
begin
// Set outline color and no fill
Canvas.Pen.Color := Color;
Canvas.Brush.Style := bsClear;
// Convert circle data to required confounding rectangle
Canvas.Ellipse(X - Radius, Y - Radius, X + Radius, Y + Radius);
end;



שימו לב שכל הפונקציות הללו נקראות באותו שם בדיוק - Draw. שוב, מדובר במרכיב מהותי בתכנות מונחה-עצמים, ובזכותו אנחנו יכולים לכתוב את הפונקציה הבאה, שעוברת על כל האלמנטים הגרפיים שברשימה ומציירת אותם. שימו לב שרוב הקוד מוקדש לצביעת הרקע, ואילו הקוד הקריטי באמת הוא בן שורה אחת בלבד:


procedure TVoronoiElementList.Draw(Const Canvas: TCanvas; const Background: TColor);
var
j : Integer;

begin
// Draw background
Canvas.Pen.Color := Background;
Canvas.Brush.Color := Background;
Canvas.Brush.Style := bsSolid;
Canvas.Rectangle(Canvas.ClipRect);
// Draw elements
for j := 0 to Count - 1 do (GetItem(j) as TVoronoiElement).Draw(Canvas);
end;

 
 
מכיוון שלא התחייבנו, כאמור, לטופס תצוגה מוגדר כלשהו, אפשר לבנות בדקה או שתיים טופס מיוחד וזמני שישמש לבדיקה של הפונקציות הנ"ל. לקחנו קובץ INI שנכתב ידנית ומכיל שישה אלמנטים (שני עיגולים, קו ושלוש נקודות), טענו אותו לרשימה וציירנו אותה על ה-TCanvas שבטופס. כך זה נראה:
 
אלמנטים גרפיים של דיאגרמת וורונוי (צילומסך: עידו גנדל)
 אלמנטים גרפיים של דיאגרמת וורונוי (צילומסך: עידו גנדל) 
 
כעת, נרצה לראות את הדיאגרמה הסופית. גם זה פשוט וקל, אחרי שיש לנו את כל הקוד הבסיסי מוכן לפעולה. נכתוב עבור כל סוג אלמנט גרפי את הפונקציה שמחשבת את המרחק ממנו לנקודה כלשהי. הנה, לדוגמה, הפונקציה לחישוב המרחק מאלמנט העיגול:



function TVoronoiCircle.Distance(const px, py: Integer; const Measure: TDistanceMeasureType = dmEuclid) : Integer;
var
dx, dy : Integer;

begin
case Measure of
dmEuclid: begin
dx := Abs(px - X);
dy := Abs(py - Y);
Result := Abs(Round(Sqrt(dx * dx + dy * dy)) - Radius);
end
else
Result := 0;
end;
end;



מה הסיפור עם המשתנה הזה, Measure, והערך dmEuclid? בינתיים כלום, אך אם בעתיד נרצה ליצור דיאגרמות בעזרת שיטת חישוב מרחק אחרת, לא אוקלידית, נוכל לנצל את התשתית שהכנו כאן ולהוסיף שיטות חישוב נוספות כאוות נפשנו, בלי יותר מדי בלגן. כעת, כשיש לנו דרך לחשב מרחק מכל אלמנט, נכתוב את הפונקציה שמוצאת, מתוך הרשימה המלאה, את האלמנט הקרוב ביותר לנקודה ומחזירה את הצבע שלו:



function TVoronoiElementList.ClosestColor(const px, py: Integer; const Measure: TDistanceMeasureType = dmEuclid) : TColor;
var
j, d, MinD : Integer;

begin
Result := clNone;
MinD := High(Integer);
for j := 0 to Count - 1 do
begin
d := (GetItem(j) as TVoronoiElement).Distance(px, py, Measure);
if d < MinD then
begin
MinD := d;
Result := (GetItem(j) as TVoronoiElement).Color;
end;
end;
end;



זה הכל. כעת, בקוד של טופס הניסוי הזמני שלנו, נכתוב את הפונקציה שעוברת על כל הפיקסלים ב-TCanvas וצובעת כל אחד בצבע של האלמנט הקרוב ביותר אליו. שימו לב לשמות חסרי המשמעות של הטופס ושל הלחצן בשורה הראשונה – תופעה בלתי נסבלת בקוד רציני, אבל מותרת כשמדובר בקוד קטן וזמני. Image1 הוא האלמנט בטופס שמכיל את ה-TCanvas ומגדיר את שטח התצוגה:



procedure TForm4.Button3Click(Sender: TObject);
var
px, py : Integer;

begin
for px := 0 to Image1.Width do
for py := 0 to Image1.Height do
Image1.Canvas.Pixels[px, py] := List.ClosestColor(px, py);
end;



הלחצן Button3 הוא האמצעי בתמונה הקודמת, זה שכתוב עליו "צייר דיאגרמה". לוחצים עליו, מחכים בערך שניה עד שהחישובים נגמרים, והנה התוצאה הסופית:

 
דיאגרמת הוורונוי המתקבלת (צילומסך: עידו גנדל)
 דיאגרמת הוורונוי המתקבלת (צילומסך: עידו גנדל) 
 
זה אחד הדברים הכיפיים בדיאגרמות וורונוי: להסתכל על האלמנטים המרכיבים ולנסות לנבא איך תיראה הדיאגרמה הסופית. לכן, ברוח האולימפיאדה המתקרבת, יצרנו עוד קובץ עם חמישה אלמנטים מסוג עיגול. איך לדעתכם תיראה הדיאגרמה הסופית?
 
איזו דיאגרמת וורונוי תתקבל מדגל האולימפיאדה? (צילומסך: עידו גנדל)
 איזו דיאגרמת וורונוי תתקבל מדגל האולימפיאדה? (צילומסך: עידו גנדל) 
 
 
 
@@@@@@@@@@@@@@@@@@@ ilan @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
@@@@@@@@@@@@@@@@@@@ ilan @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
 
תגובות
הוסף תגובה0 תגובות
הוספת תגובה
מאת
 
נושא
 
תוכן
 
 
 
 
תודה! תגובתך התקבלה.
התגובה תתפרסם בכפוף לתנאי האתר.
 
 
 
 
 

כל הזכויות שמורות 2011 © נענע 10 בע"מ
 
 
 
 
כל הזכויות שמורות © Nana10 בע"מ
Video powered by