ראשי
עדכון תוכנות
התוכנות : Function Drawer - צייר פונקציות 2D ו 3D Function Drawer - צייר פונקציות 3D עודכנו. החידוש המרכזי בשניהם הוא אופן ניתוח הפונקצייה המוכנסת , וכעת אין צורך יותר לחכות לחישובים כי הם מתבצעים במהירות גבוהה מאוד. |
אלגוריתם פשוט ליצירת אש
לפני כמה חודשים שמעתי על אלגוריתם פשוט ליצרת אנימצייה של אש. לאחורנה יישמתי אותו, ולהפתעתי הוא פעל מצויין ביחס לקלות שלו. זה יהיה מדריך קצר, שבסופו יפורסם התוכנה הסופית. האלגוריתם אש, אמורה להיות רנדומלית ומורכבת מצבעים חמים בחיים, כאשר הצבע החם ביותר לצורך העניין יהיה לבן, והקר ביותר יהיה שחור (בינהם יהיה אדום וכתום). כעת נעבור לדבר על פיקסלים, הרי עליהם נצייר את האש. לכל פיקסל יהיה ערך מספרי, בין 0 ל100 שייצג את ה"חום" של הפיקסל, ממנו נגזור את הצבע מאוחר יותר. השכבה הראשונה של הפיקסלים (מלמטה) תהיה שכבה שבה הפיקסלים יוגרלו האם הם יהיו לבנים או שחורים. אנחנו יודעים הרי שכאשר מסתכלים מלמטה למעלה באש נראה כי היא "מתקררת". בהתחלה היא לבנה, אחר כך היא צהובה, לאחר מכן כתומה ובסוף אדומה. לכן, ככול ששורת הפיקסלים עולה, נצתרך להפחית מהפיקסל את החום שלו. אז את השורה הראשונה מלמטה הבנו, אבל איך נקבע את השורה שמעליה? וכן הלאה? כאן נכנס האלגוריתם, והוא פשוט מאוד. כל פיקסל הוא ממוצע של שלושת הפיקסלים שבשורה מתחתיו. באופן הבא: אם אנחנו רוצים לדעת את החום של הפיקסל (x, y) נתצרך לחשב את הממוצע של הפיקסלים (x+1, y+1) , (x, y+1) , (x-1, y+1) ולחסר איזה שהוא קבוע (לצורך ההדגמה 1). וזהו. זה בגדול כל העקרון של האלגוריתם :) הקוד טוב סיימנו לדבר באוויר, בו נכתוב קצת קוד. משתנים: 1. מערך דו מימדי שיכיל את את החום של הפיקלסים. לכל גודלו יהיה הרוחב והאורך של הpicturebox שעלייה נציג את האש. 2. מערך בגודל של 100 שיכיל את הצבעים לבן כתום אדום ושחור בהדרגה . (כאשר לבן זה 100 ושחור זה 0). 3. אובייקט מסוג Random. מתודות: 1. אתחול המשתנים (אתחול המערך, אתחול המשתנה הרנדומלי..) 2. בניית המערך של הצבעים. 3. קביעת השורה הראשונה באופן רנדומלי. 4. עדכון כל שאר השורות. אתחול המשתנים יתבצע באופן הבא: rand = new Random(); // אתחול אובייקט מסוג Random GetColorArray(Color.Black, Color.Red, Color.Orange, Color.White); // עדכון המערך של הצבעים. pixelColors = new float[picbox.Width, picbox.Height]; // אתחול מערך הפיקסלים.בניית מערך הצבעים יעשה בצורה הבאה: public void GetColorArray(Color c1, Color c2, Color c3, Color c4) { Color[] colors = new Color[100]; for (int i = 0; i < 33; i++) { double f = i / 32.0; colors[i] = Color.FromArgb( clamp((int)(c1.R + (f * (c2.R - c1.R)))), clamp((int)(c1.G + (f * (c2.G - c1.G)))), clamp((int)(c1.B + (f * (c2.B - c1.B)))) ); } for (int i = 33; i < 66; i++) { double f = (i - 33) / 32.0; colors[i] = Color.FromArgb( clamp((int)(c2.R + (f * (c3.R - c2.R)))), clamp((int)(c2.G + (f * (c3.G - c2.G)))), clamp((int)(c2.B + (f * (c3.B - c2.B)))) ); } for (int i = 66; i < 100; i++) { double f = (i - 66) / 32.0; colors[i] = Color.FromArgb( clamp((int)(c3.R + (f * (c4.R - c3.R)))), clamp((int)(c3.G + (f * (c4.G - c3.G)))), clamp((int)(c3.B + (f * (c4.B - c3.B)))) ); } this.colors = colors; }הפונקצייה clamp דואגת שהערך לא יהיה גבוהה מ225 ולא יהיה נמוך מ0. הפונקצייה אשר תגריל את השורה הראשונה תראה כך: private void RandFirstLine() { int y = picbox.Height - 1; for (int x = 20; x < picbox.Width-20; x++) { if (rand.Next(10) == 0) pixelColors[x, y] = 0; if (rand.Next(10) == 0) pixelColors[x, y] = 99; } }והפונקצייה האחרונה אשר תעדכן את שאר השורות (ובעצם הטיימר יקרא לה) ותרנדר: public void UpdateFire() { Bitmap bitmap = new Bitmap(picbox.Width, picbox.Height); RandFirstLine(); for (int x = 1; x < pixelColors.GetLength(0) - 1; x++) for (int y = 0; y < pixelColors.GetLength(1); y++) { if (y < pixelColors.GetLength(1) - 1) pixelColors[x, y] = (pixelColors[x, y + 1] + pixelColors[x - 1, y + 1] + pixelColors[x + 1, y + 1]) / 3 - 1; if (pixelColors[x, y] > 99) pixelColors[x, y] = 99; if (pixelColors[x, y] >= 0) bitmap.SetPixel(x, y, colors[(int)pixelColors[x, y]]); } picbox.Image = bitmap; }וזהו, התוכנה מוכנה! הורדה של התוכנה הסופית עם עוד כמה אפשרויות: http://sites.google.com/site/yonisprogs/programs/Fire.rar |
משחק החיים ואוטומטים תאיים
עוד מדריך איך לבנות את משחק החיים. המדריך הזה יהיה יותר קצר, עם פחות קוד, ככה שישאיר לכם את המחשבה איך לפתח אותו. רמת קושי: קל-בינוני ידע דרוש: טיפה GUI (פרוייקטים בwindows forms) , ובסיס של תכנות מונחה עצמים. קוד המקור המלא יתפרסם בסוף המדריך. היסטוריה אז מה זה בעצם משחק החיים ואוטומט תאי? ויקיפדיה: אוטומט תאי הוא מודל הנחקר במסגרת תורת החישוביות, מתמטיקה וביולוגיה תאורטית. הוא כולל סריג (או באופן כללי גרף) של תאים, שלכל אחד מהם מספר סופי של מצבים. הזמן במודל הוא בדיד, ומצבו של כל תא בזמן נתון t הוא פונקציה של מצבו ומצב תאים אחרים (שכניו של התא הנתון) בזמן t-1. פונקציה זו חלה על כל התאים, כלומר כל התאים משתנים על-פי אותה מערכת של כללים. כל הפעלה של הפונקציה על כל התאים בסריג יוצרת דור חדש. במילים פשוטות שקשורות למתכנת, אוטומט תאי זה נושא בתכנות, שמתאר מערכות של תאים שפועלים על פי אותם החוקים. משחק החיים היא הדוגמא הנפוצה ביותר לכך, אך בעצם הוא לא באמת משחק. הומצא ע"י ג'ון קונווי. מהלך המשחק ב"משחק" יש לנו מערך דו מימדי של תאים, שלכל תא יש 2 מצבים: חי או מת.תחילה, המשתמש בוחר אילו תאים יחיו\המחשב מגריל באופן רנדומלי ואז המשחק מתחיל. כל תא במשחק כפוף לחוקי המשחק, והמצב שלו (חי\מת) תלוי אך ורק בשכניו (השמונה משבצות המקיפות את התא) . בכל שלב (דור) במשחק, כל תא מקבל מצב חדש או נשאר במצבו הקודם (לפי החוקים) אלו החוקים של משחק החיים:
משחק זה מהווה אלמנט קטן של בינה מלאכותית. המשחק העסיק הרבה אוניברסיטאות למינהן, הן ביולוגים והן מתכנתים. המשחק יכול להיות מחזורי, לפעמים לא נגמר, לפעמים נשאר במצבו ולפעמים כל התאים מתים. הכול תלוי רק בתנאי ההתחלה. כמו במדריך הקודם, מאוד נוח לכתוב תחילה אלגוריתם ורק אחר כך להמיר אותו לשפת תכנות. אני אישיתי מעדיף לבנות מחלקה בשם Cell ולאחר מכן ניצור מערך דו-מימדי של Cellים. נוח מאוד להתייחס אל כל תא כאובייקט Cell מאשר משתנה int ממערך דו מימדי, ובנוסף אנחנו צריכים עוד מידע על תא חוץ ממצבו (חי\מת) א.ניצור מערך דו מימדי של Cellים ונאתחל אותם. ב. ניתן אפשרות למשתמש לבחור אילו תאים יהיו חיים, או שנבחר הגרלה. // דור ראשון : ג. בעבור כל תא ותא במערך הדו מימדי שלנו: ג.1. נחשב את מספר השכנים שלו ד.בעבור כל תא ותא במערך הדו מימדי שלנו: ד.1.במידה ולתא יש יותר מ5 שכנים או פחות משכן אחד ד.1.א שנה את מצבו למת. ד.2. אחרת אם לתא יש 3 שכנים בדיוק: ד.2.א. שנה את מצב התא לחיי. ה.הצג למשתמש את המערך . ו.במידה ורצוי עוד דור, בצע את ג. תכנות תחילה, ניצור לנו מחלקה בשם Cell, עם משתנה מסוג int בשם neighbors שימנה את מספר השכנים של התא, ומשתנה מסוג bool בשם alive שיכיל את המצב של התא. נכתוב גם בנאי שיאתחל את האובייקט. class Cell { public int neighbors; public bool alive; public Cell()
} בפורום שלנו נצתרך להכריז על מערך דו מימדי של Cell בשם cells ולאתחל שלנו (אפשר בבנאי של הפורום או באוונט של Form_Load) ניתן למשתמש כפתור שלחיצה עליו תוביל להגרלת החיים בין התאים. ניתן לעשות זאת בצורה הבאה: Random random = new Random();foreach (Cell c in cells) c.alive = random.Next(2) == 0; // כיוון שאנחנו נותנים ערך למשתנה בוליאני, נוכל לתת לו ביטוי בוליאני.ניצור טיימר ו3 פונקצייה אשר ירוצו כל Tick של הטיימר. להלן התיאור של כל פעולה: CalcCellsNeighbors() - יחשב בעבור כל תא את מספר השכנים החיים שלו. UpdateState() - יעדכן את מצב התא לפי מספר השכנים שלו. Drawcells() - יציג למשתמש את התאים החיים והמתים. פירוט האלגוריתם בעבור כל פונקצייה. CalcCellsNeighbors() : חישוב השכנים השכנים יתבצע בדרך הבאה: בעבור כל תא במערך אנחנו צריכים לבדוק האם כל אחד מהשמונה שכנים של תא חיי, במידה וכן להגדיל את הערך של הneighbors באחד. נעזר ב2 לולאות for. בעבור תא (i, j) נצתרך לבדוק את מצב התאים: (i-1,j-1) (i-1, j) (i-1, j+1) (i, j-1) (i, j+1) (i+1, j-1) (i+1, j) (i+1, j+1) יש לזכור!: כיוון שיש תאים שנמצאים בקצה הלוח, פשוט לא קיימים להם שכנים ולכן אם ננסה לגשת אל איבר שלא נמצא במערך שלנו התוכנה תקרוס. כאשר תכתבו את זה, זכרו להוסיף תנאי, לדוגמא: לפני שבודקים את האיבר שמצד ימין בשורה הנוכחית (i, j+1) עלינו לבדוק שj+1 לא חורג מגבולות המערך. UpdateState() : מהחוקים שפורסמו בהתחלה ניתן להסיק את שני התנאים הבאים: (1) במידה ומספר השכנים של התא גדול מ3 או קטן מ2 :שנה את מצב התא למת. (2) אחרת במידה ולתא יש בדיוק שלושה שכנים: שנה את מצב התא לחיי. הקוד יהיה בצורה הבאה: foreach (Cell c in cells){ if (c.neighbors < 2 || c.neighbors > 3) c.state = State.dead; else if (c.neighbors == 3) c.state = State.alive;}זה כל החלק של החשיבה במשחק, וכעת נותר רק החלק הגרפי. ניתן לממש זאת בהרבה שיטות, אני אישית עשיתי זאת בעזרת PictureBox. בניית ה"רשת": צרו PictureBox מקמו אותו באמצע הפורום. צרו את האוונט Paint שלו. במידה ויהיה לנו 20 תאים על 20 תאים, נוכל ליצור לולאה עם המשתנה i שערכו יהיה בין 0 ל20 בקפיצות של 1. נעזר במתודה: e.Graphics.DrawLine המציירת קו בין שתי נקודות. נשתמש בה פעמיים, פעם אחת לציור קו אופקי ופעם אחת אנכי. על מנת לצייר קו אופקי נצתרך לעדכן רק את רכיב הy של שני הנקודות, כיוון שתמיד רכיבי הx יהיו 0 והגודל והרוחב של הPictureBox. בשני הנקודות ערכי הy יהיו שווים ל: (picbox.Height-1)* (i / 20)באופן זה המרחקים בין הקווים יהיו שווים, כאשר i=0 נתחיל מרכיב y = 0 וכאשר i=20 הקו יצוייר בפיקסל האחרון של הPictureBox. באופן דומה אפשר לעשות זאת גם לכיוון האנכי. במידה ולא עובד לכם, נסו לכתוב בסוף המתודה : picbox.Invalidate();Drawcells() - מילוי התאים החיים: נצתרך ליצור משתנה מסוג Graphics ולאתחל אותו לGraphics.FromeImage ובסוגריים את התמונה של הPicbox : Graphics gr = Graphics.FromImage(picbox.Image);כעת האובייקט gr יוכל לשלוט על התמונה ולצייר עלייה עוד דברים (לדוגמא תאים חיים). כיוון שאנחנו נצייר בכל דור ודור מחדש את התאים, עלינו לאתחל את התמונה. נוכל "לנקות" אותה לצבע של הפורום, וכך היא תהיה כמו חדשה. (ניתן גם לאתחל את התמונה לBitmap חדשה.) gr.Clear(this.BackColor);כעת נעזר ב2 לולאות for ונעבור על כל תא במערך.במידה והתא חיי, תצתרך לצייר ריבוע במקומו. נעזר במתודה gr.FillRectangle שמקבלת מברשת ומלבן. את המברשת נוכל לאתחל לצבע שחור, ואת הגודל והמיקום של המלבן אני אשאיר לכם לחשוב. תעזור בi וj, ובאורך ורוחב של הPictureBox. כיוון שאתחלנו את התמונה הרשת נעלמה לנו, נצתרך לקרוא לפונקצייה לבסוף. טיימר: ניצור טיימר (נוח לגרור אותו מהToolBox אל הפורום). נגדיר את הInterval שלו ל100, ובאוונט Tick שלו נצתרך לקרוא לשלושת המתודות שבנינו: CalcCellsNeighbors() - יחשב בעבור כל תא את מספר השכנים החיים שלו. UpdateState() - יעדכן את מצב התא לפי מספר השכנים שלו. Drawcells() - יציג למשתמש את התאים החיים והמתים. ניתן למשתמש כפתור שכאשר הוא ילחץ עליו הטיימר יתחיל: timer1.Start(); ועוד כפתור שהטיימר יעצור: timer1.Stop(); זהו! התוכנה הושלמה. במידה ונתקלתם בבעייה: יצירת קשר התוכנה המלאה שבניתי, שכוללת עוד חוקים מלבד משחק החיים: קוד המקור: http://sites.google.com/site/yonisprogs/programs/CellularAutomatonsource.rar |
קבוצת מנדלברוט - מדריך C#
![]() ![]() מדריך לבנות סימולציה גיאומטרית לקבוצת מנדלברוט. מדריך איך לבנות תוכנה אשר נותנת את היצוג הגרפי של קבוצת מנדלברוט. נכתב בהשראת: מדור של המגזין לפרק את הבייט. רמת קושי:בינוני ידע דרוש: שליטה בGUI (פרוייקטים בwindows forms) , ידע בסיסי של תמונת bitmap וצבעי הRGB, וטיפה מתמטיקה (יהיה הסברים להכול אבל מכיוון שזה במספרים מרוכבים זה לא תמיד יהיה ברור). אז מה זה קבוצת מנדלברוט? (ולמה שווה לבנות תוכנה כזאתי?): קבוצת מנדלברוט היא קבוצה של מספרים מרוכבים אשר הגבול של ייצוגן הגאומטרי מהווה את אחת הדוגמאות המוכרות ביותר של פרקטלים במתמטיקה. קבוצת מנדלברוט הומצאה על ידי בנואה מנדלברוט בשנת 1979. מתוך ויקיפדיה, לחצו על הלינקים אם יש משהו לא מובן. במילים פשוטות, לפני כ30 שנה, גילו שכאשר מציגים את הייצוג הקרטזי בישור של קבוצה של מספרים (מרוכבים במקרה שלנו) שמקיימים איזה שהוא חוק שקשור לסדרות מקבלים פרקטל. אם נבהלתם מהמושגים אל תדאגו, זה הרבה יותר פשוט ממה שזה נשמע.
זה החלק המתמטי של המדריך, אין צורך להבין , אבל צריכים לדעת מה אתם עושים. מספר מרוכב - מספר אשר מכיל 2 רכיבים בניגוד למספר "רגיל" (ממשי כמו 4, 2.5 , שורש 2, e, 0). המספרים המורכבים הומצאו כאשר מתמטיקאים ניסו למצוא את הפתרון למשוואה: . כמובן שלמשוואה הזאתי אין פרתון "רגיל" (ממשי) כיוון שכולנו יודעים שכל מספר שנעלה בריבוע יהיה מספר חיובי (או 0). לכן הומצא המספר i לפי ההגדרה: . לפי הכלל הזה, לכל מספר נוסף רכיב . למעשה, מספר מורכב מכיל בתוכו רכיב ממשי (מספר רגיל) ורכיב "מדומה". מספר מדומה נכתב בצורה הבאה: . כאשר Z מספר מרוכב, a וb ממשיים.כל מספר מרוכב ניתן להצגה במישור של גאוס, שבו הציר האופקי זה ציר המספרים הממשיים, והאופקי זה הציר המדומה. לכל כל מספר מרוכב יכול להיות מוצג כנקודה בעל הקורדינטות (a,b). כאשר מסתכלים על סדרת הנסיגה (סדרה שבה כל איבר מוגדר בעזרת האיבר הקודם) הבאה:
מגלים משהו מוזר ביותר. כאשר אנחנו מדברים על מספרים ממשיים, זה ברור שהסדרה הנ"ל תתבדר (תגיע) לאינסוף. הרי ככול שהמספר הקודם יותר גדול, ככה המספר הבא יהיה הרבה יותר גדול וכן הלאה. אבל כאשר אנחנו מדברים על מספרים מרוכבים קוראת תופעה מאוד מעניינת. יש מספרים, שהסדרה לא תגיע לעולם לאינסוף! (למעשה היא תישאר בטווח סגור שלא תוכל לעבור אותו). כל מספר Z0 שמקיים את הכלל הזה נכנס לקבוצת מנדלברוט. זה אומר שבעבור Z0= (x1, y1) a הסדרה תשאף לאינסוף לאט לאט אך בטוח, ועבור Z0= (x2, y2) a הסדרה לא תגיע לאינסוף לא משנה כמה איברים נחשב! (סדרה חסומה) אם החישובים מצביעים על כך שהסדרה בעבור (x2, y2) לא מגיע בסופו של דבר לאינסוף אז הנק' (x2, y2) היא בקבוצת מנדלברוט. אם לא הבנתם משהו, אני מציע לקרוא בויקיפדיה או לדלג ולנסות להבין מאוחר יותר. מכאן התוכנה כבר הרבה יותר פשוטה. ![]() העקרונות של התוכנה: כל מספר מרוכב הוא פיקסל. כמו שאנחנו יודעים לפיקסל יש קורדינאטות x,y שמסתדר מצויין עם מספר מרוכב. כל שעלינו לדעת הוא האם המספר הn של הסדרה המרוכבת שלנו מגיע לאינסוף או לא. גודל של מספר מרוכב מוגדר כערך המוחלט שלו ומחושב ככה: כביכול פיתגורס של הרכיב בציר האופקי ובציר האנכי. לכן עלינו לבדוק האם שורש של סכום הריבועיים עובר את אינסוף. כיוון שזה לא ממש אפשרי, אנחנו נבצע חישוב נומרי (מקורב) על מנת לדעת האם הפיקסל שבחרנו נמצא בקבוצה או לא.יש לשים לב שכאשר מעלים את המספר x+iy בריבוע אנחנו מקבלים לפי נוסחאת הכפל המקוצר את הביטוי הבא: (x+iy)^2 = x2 + 2xiy +i2y2 ואנחנו יודעים שi^2 שווה ל1-. לכן: (x+iy)^2 = x2 + 2xiy - y2 כך שהרכיב האופקי (ממשי) והאנכי (מדומה) של כל איבר בסדרה יתואר ככה (כל רכיב שהוא כפול i נחשב למדומה): כאשר Zn+1.r זה הרכיב הממשי של האיבר, Zn+1.i זה הרכיב המדומה של האיבר, Zn.r זה הרכיב הממשי של האיבר הקודם, Zn.i זה הרכיב הממשי של האיבר הקודם, Z0.r זה הרכיב הממשי של האיבר הראשון בסדרה. איך נעשה את זה באופן מקורב? בשיטה הבאה: א. אנחנו לא נציב כל פיקסל אינסוף פעמים בנוסחאת הסדרה, אלא רק 1000 פעמים. אנחנו מניחים שאם הסדרה לא מגיעה לאינסוף אחרי 1000 פעמים היא לא תגיע לשם. ב. אנחנו לא נבדוק האם הגודל של האיבר הנוכחי בסדרה הוא אינסוף, אלא מספיק לנו לבדוק אם הוא עובר את 2 או לא. אנחנו מניחים שאם הוא עובר את 2 הסדרה תתבדר לאינסוף. ג.נבדוק את המספרים המרוכבים בטווח של 2.2- עד 0.8 בציר הממשי (ציר x) ומ1.2- עד 1.2 בציר המדומה (ציר y). בטווח הזה ה"אקשן" קורה. ![]() בדרך זאתי, נוכל למצוא את האיברים של סדרת מנדלברוט. כל פיקסל כזה, יצבע בצבע שחור ומה שנקבל זה התמונה מצד ימין. דבר ראשון מדהים לראות איך סדרת נסיגה מאוד פשוטה ויסודית מייצרת תמונה אשר נמצאת בטווח מסויים, ובקרוב נדע כמה היא מיוחד. התמונה הזאתי היא פרקטל, אחד המפורסמים ביותר אם לא הכי. ניתן לראות את העקרון של הפרטל באנימציה הזאתי. לא משנה כמה מתקרבים, אנחנו נקבל את אותה הצורה, ביחד עם עוד עשרות עקומות מסולסלות. זה (בערך) מה שראה מנדלברוט לפני שלושים שנה, כאשר הריץ את הנוסחא שלו בתוך מחשב ישן. למרות התמונה היפה שקיבלנו מימין, אנחנו רוצים את התמונה ה"צבעונית". אז אנחנו שואלים מאיפה הצבעים הגיעו? אז זה עניין פשוט, אני אפרט עליו יותר בקוד עצמו, אבל העקרון הוא כזה: כאשר הסדרה שואפת מהר מאוד לאינסוף (ממש אחרי הצבות בודדות בנוסחא) צבעו של הפיקסל (אני מזכיר האיבר הראשון של הסדרה 0) יהיה סגול (צבע "קר"). במידה והסדרה תשאף לאינסוף לאט, למשל כשנגלה את זה רק בחזרות האחרונות בבדיקה שלנו (1800-200) צבעו של הפיקסל יהיה אדום (צבע "חם"). זה העיקרון, כל שאר הצבעים הם עובדים על אותו העיקרון, ככול שסדרה תשאף יותר מהר לאינסוף הצבע יהיה יותר קרוב לסגול, וככול שהסדרה תשאף לאט (כביכול כמעט בקבוצה) הצבע יהיה אדום. אנחנו ניעזר בפונקצייה הממירה בין אורך גל (הצבע הסגול - 380 הצבע האדום - 740) לבין RGB. (אני אתן אותה בקוד). ואיך כל זה הופך לC#? תחילה נבנה אלגוריתם בעברית אשר יתאר את אופן פעולת התוכנה. 1. לולאה (ליתר דיוק 2 כי מדובר בפיקסלים) אשר תעבור על המספרים המרוכבים האיברים שבטווח שנבחר (הברירת מחדל היא 2.2- עד 0.8 בציר הממשי (ציר x) ומ1.2- עד 1.2 בציר המדומה (ציר y))
זה האלגורתים עבור שחור לבן, למרות שבעבור צבעוני זה בכלל לא הרבה יותר מסובך. אז בו ניגש לקוד :) תחילה ניצור Button וPictureBox. בדוגמא לכפתור קוראים btnDraw, ולקופסאת תמונה קוראים picbox. ניצור פונקצייה בשם Draw אשר הפרמטרים שלה זה double startR, double endR, double startI, double endI, int maxloops. private void Draw(double startR, double endR, double startI, double endI, int maxloops) { }נכריז על המשתנים שנשתמש בהם (אנחנו זקוקים למשתנים double ולא float כיוון שכשנרצה להתקרב נצתרך דיוק), ונבנה את הלולאה שבסעיף 1 של האלגוריתם: private void Draw(double startR, double endR, double startI, double endI, int maxloops) { double Znr, Zni, tempZnr; // הכרזה על משתנים bool infinite; for (double Z0r = startR; Z0r < endR; Z0r += (endR - startR)/picbox.Width) // לולאה של הציר הממשי כיוון אופקי for (double Z0i = startI; Z0i < endI; Z0i += (endI - startI) / picbox.Height) // לולאה של הציר המדומה ציר אנכי { } }נאתחל את הערכים של Zni וZnr לערכים ההתחלתיים ונבנה ללואה נוספת. בלולאה החדשה אנחנו נצרתך לעדכן את הערכים של Znr וZni לפי הנוסחאות: private void Draw(double startR, double endR, double startI, double endI, int maxloops) { double Znr, Zni, tempZnr; bool infinite; for (double Z0r = startR; Z0r < endR; Z0r += (endR - startR)/picbox.Width) for (double Z0i = startI; Z0i < endI; Z0i += (endI - startI) / picbox.Height) { Znr = Z0r; // איפוס המשתנים לערכים ההתחלתיים על מנת להתחיל את הסדרה Zni = Z0i; infinite = false; // איפוס המשתנה הבוליאני שיקבע האם המספר המרוכב נמצא בסדרה for (int i = 0; i < maxloops; i++) { tempZnr = Znr; // אתחול משתנה עזר. Znr = Znr * Znr - Zni * Zni + Z0r; // הצבה בנוסחא שנותנת את הרכיב האופקי Zni = 2 * tempZnr * Zni + Z0i; // הצבה בנוסחא שנותנת את הרכיב האנכי } } }עכשיו כל שנותר הוא לבדוק האם הגודל של האיבר הנוכחי גדול מ2 לפי הנוסחא הבאה: . כמובן שנוכל להעלות את 2 בריבוע במקום לעשות שורש. private void Draw(double startR, double endR, double startI, double endI, int maxloops) { double Znr, Zni, tempZnr; bool infinite; for (double Z0r = startR; Z0r < endR; Z0r += (endR - startR)/picbox.Width) for (double Z0i = startI; Z0i < endI; Z0i += (endI - startI) / picbox.Height) { Znr = Z0r; Zni = Z0i; infinite = false; for (int i = 0; i < maxloops; i++) { tempZnr = Znr; Znr = Znr * Znr - Zni * Zni + Z0r; Zni = 2 * tempZnr * Zni + Z0i; if (Znr * Znr + Zni * Zni >= 4) // בדיקת הגודל של המספר { infinite = true; // במידה והתוכנה ככול הנראה תגיע לאינסוף אנו משנים את ערכו של המשתנה הבוליאני לאמת - המספר לא בקבוצה break; // אין צורך יותר לחשב עבור המספר הזה, כיוון שהוא לא בקבוצה } } if (!infinite) // תנאי שבודק האם לפי החישובים האיבר נמצא בקבוצה או לא { } } }כעת אנחנו מוכנים מבחינת החישובים. אנחנו צריכים עכשיו לשנות פיקסל בתמונה. בשביל לעשות את זה נגדיר בתחילת הפונקצייה אובייקט מסוג bitmap ונאתחל אותו. בנוסף , נאפס את כל הפיקסלים לצבע לבן בתנאי שנכתב באלגוריתם כסעיף 1.2 נצתרך לעדכן את אחד הפיקסלים בbitmap לצבע שחור. המעבר בין הרכיבים של המספר המרוכב לבין הפיקסל בתמונה אולי נראה מסורבל, אבל מה שקורה זה שבעצם מחשבים את המיקום של כל רכיב של האיבר באחוזים מההתחלה, ואז כופלים בגודל של התמונה. כמובן שצריכים להמיר את מה שיוצא לint. לאחר מכן צריכים לעדכן את התמונה של הPictureBox, ולבסוף לצייר מחדש אותה. הקוד הסופי של הפונקצייה נראה ככה: private void Draw(double startR, double endR, double startI, double endI, int maxloops) { double Znr, Zni, tempZnr; bool infinite; Bitmap image = new Bitmap(picbox.Width, picbox.Height); // תמונת ביטמפ חדשה Graphics.FromImage(image).Clear(Color.White); // איפוס הפיקסלים ללבן for (double Z0r = startR; Z0r < endR; Z0r += (endR - startR)/picbox.Width) for (double Z0i = startI; Z0i < endI; Z0i += (endI - startI) / picbox.Height) { Znr = Z0r; Zni = Z0i; infinite = false; for (int i = 0; i < maxloops; i++) { tempZnr = Znr; Znr = Znr * Znr - Zni * Zni + Z0r; Zni = 2 * tempZnr * Zni + Z0i; if (Znr * Znr + Zni * Zni >= 4) { infinite = true; break; } } if (!infinite) image.SetPixel((int)(((Z0r - startR) / (endR - startR)) * picbox.Width), (int)(((Z0i - startI) / (endI - startI)) * picbox.Height), Color.Black); } picbox.Image = image; // החלפת התמונה הקיימת לביטמפ picbox.Invalidate(); // ציור מחדש של הפקד }החלק הראשון של התוכנה (שחור לבן) כמעט הושלם! עלינו ליצור event של לחיצה על כפתור שיקרא לפונקצייה שלנו. תלחץ פעמיים על הכפתור, ואוטומטית תהיה לנו את הפונקצייה של האוונט. נשנה אותה לקוד הבא: private void btnDraw_Click(object sender, EventArgs e) { Draw(-2.2, 0.8, -1.2, 1.2, 1000); // קריאה לפונקצייה שבנינו עם הערכים של הברירת מחדש }כעת החלק הראשון הושלם. חלק השני, הוא הוספה קטנה לתוכנה, שתגדיר לה להיות עוד צבעים חוץ משחור (נמצא בקבוצה) וריק\לבן לא נמצא בתמונה. כמו שכבר הזכרתי, הצבע נקבע לפי המהירות שבא הסדרה שואפת לאינסוף. ככול שיותר מהר הצבע יותר קר. תחילה, נשתמש בשני פונקציות העזר הבאות, שהולכות להמיר לנו אורך גל לRGB (הפירוט שלהם הוא הצבה בפונקצייה בטווחים שונים) private Color getColorFromWaveLength(int Wavelength) { double Gamma = 1.00; int IntensityMax = 255; double Blue; double Green; double Red; double Factor; if (Wavelength >= 350 && Wavelength <= 439) { Red = -(Wavelength - 440d) / (440d - 350d); Green = 0.0; Blue = 1.0; } else if (Wavelength >= 440 && Wavelength <= 489) { Red = 0.0; Green = (Wavelength - 440d) / (490d - 440d); Blue = 1.0; } else if (Wavelength >= 490 && Wavelength <= 509) { Red = 0.0; Green = 1.0; Blue = -(Wavelength - 510d) / (510d - 490d); } else if (Wavelength >= 510 && Wavelength <= 579) { Red = (Wavelength - 510d) / (580d - 510d); Green = 1.0; Blue = 0.0; } else if (Wavelength >= 580 && Wavelength <= 644) { Red = 1.0; Green = -(Wavelength - 645d) / (645d - 580d); Blue = 0.0; } else if (Wavelength >= 645 && Wavelength <= 780) { Red = 1.0; Green = 0.0; Blue = 0.0; } else { Red = 0.0; Green = 0.0; Blue = 0.0; } if (Wavelength >= 350 && Wavelength <= 419) { Factor = 0.3 + 0.7 * (Wavelength - 350d) / (420d - 350d); } else if (Wavelength >= 420 && Wavelength <= 700) { Factor = 1.0; } else if (Wavelength >= 701 && Wavelength <= 780) { Factor = 0.3 + 0.7 * (780d - Wavelength) / (780d - 700d); } else { Factor = 0.0; } int R = this.factorAdjust(Red, Factor, IntensityMax, Gamma); int G = this.factorAdjust(Green, Factor, IntensityMax, Gamma); int B = this.factorAdjust(Blue, Factor, IntensityMax, Gamma); return Color.FromArgb(R, G, B); } private int factorAdjust(double Color, double Factor, int IntensityMax, double Gamma) { if (Color == 0.0) { return 0; } else { return (int)Math.Round(IntensityMax * Math.Pow(Color * Factor, Gamma)); } }כעת נעשה כמה שינויים בפונקצייה Draw שבנינו. אנחנו נצתרך לזכור תוך כמה זמן קבענו שהמספר המרוכב לא נמצא בסדרה, ולפי זה לקבל את הצבע. התחום של האור הנראה הוא בין 370 ל740. private void Draw(double startR, double endR, double startI, double endI, int maxloops) { double Znr, Zni, tempZnr; bool infinite; int iterates = 0; // משתנה שיספור את כמות הלולאה שהמספר עוד לא הגיע לאינסוף Bitmap image = new Bitmap(picbox.Width, picbox.Height); Graphics.FromImage(image).Clear(Color.White); for (double Z0r = startR; Z0r < endR; Z0r += (endR - startR)/picbox.Width) for (double Z0i = startI; Z0i < endI; Z0i += (endI - startI) / picbox.Height) { Znr = Z0r; Zni = Z0i; infinite = false; for (int i = 0; i < maxloops; i++) { tempZnr = Znr; Znr = Znr * Znr - Zni * Zni + Z0r; Zni = 2 * tempZnr * Zni + Z0i; if (Znr * Znr + Zni * Zni >= 4) { infinite = true; iterates = i; // שמירת הערך של i break; } } if (!infinite) image.SetPixel((int)(((Z0r - startR) / (endR - startR)) * picbox.Width), (int)(((Z0i - startI) / (endI - startI)) * picbox.Height), Color.Black); else image.SetPixel((int)(((Z0r - startR) / (endR - startR)) * picbox.Width), (int)(((Z0i - startI) / (endI - startI)) * picbox.Height), getColorFromWaveLength((int)(370 + ((double)iterates/maxloops)*(740-370)))); // במידה והמספר לא בקבוצה, צייר אותו במיקום הנכון בספקטרום באופן יחסי למספר החזרות שעבר. } picbox.Image = image; picbox.Invalidate(); }לא מרוצים מהצבעים? הדרך הכי טובה בשביל לראות את הצבעים הכי טוב , זה לשנות את הערך של maxloops כאשר שולחים אותו ל50. ככה תקבלו תמונה מלא בצבעים. הדבר היפה ביותר בציור, הוא שככול שנכנסים יותר (זום) מוצאים עוד דברים מדהימים!. תוכלו להתקרב בעזרת שינוי הערכים startR, endR, startI, endI. מה שנותר לעשות, הוא ליצור ממש נוח, שבו המשתמש יוכל להקליד את הטווחים, והתמונה תצוייר לפי הקלט מהמשתמש. בנוסף אפשר לבנות אפשרות לתת למשתמש להתקרב באמצעות דאבל קליק על המיקום בתמונה. הורדה של התוכנה המלאה (עוד סוגי נוסחאות, אפשרות שמירה ועוד): http://sites.google.com/site/yonisprogs/programs/MandelbrotSet.rar זהו! התוכנה מוכנה , מקווה שנהנתם והמדריך היה מובן וברור מספיק. אשמח מאוד למשוב, ניתן ליצור קשר בדף יצירת קשר. |
אז על מה האתר בעצם?
האתר הזה הוא בעיקרון בלוג תכנות בשפת C#. אני אשתף כאן תוכנות שבניתי, רעיונות שיש לי, קודי מקור של תוכנות ומדריכים לבנות תוכנות. אני תלמיד תיכון, ככה שהרמה שלי לא גבוהה, וחלק גדול מהאתר הזה הוא בשבילי (שאני ילמד), ככה שאם משהו שתראו כאן לא ימצא חן בעינכם, או שתחשבו שיש דרך טובה יותר לעשות משהו, אני יותר מאשמח אם תצרו איתי קשר. אני אנסה לתת תוכנות כמה שיותר מעניינות, אשר משלבות לוגיקה ויצרתיות, ולהציג דברים טיפה יותר מעניינים ממה שבדרך כלל עושים בתיכון, אך ברמה לא מסובכת בכלל (אני אישית לומד C# פחות משנה). הבלוג מעוניין למתחילים שרוצים ללמוד ולדעת כל מיני צדדים של תכנות, שהוא לא רק קוד. אני אישית מאוד אשמח אם למישהו יש רעיונות לתת לי, או הערות. שיהיה בהצלחה :) |
1-6 of 6




.
.
. כאשר Z מספר מרוכב, a וb ממשיים.
(כאשר Z0 הוא האיבר הראשון בסדרה.) 
כביכול פיתגורס של הרכיב בציר האופקי ובציר האנכי. לכן עלינו לבדוק האם שורש של סכום הריבועיים עובר את אינסוף. כיוון שזה לא ממש אפשרי, אנחנו נבצע חישוב נומרי (מקורב) על מנת לדעת האם הפיקסל שבחרנו נמצא בקבוצה או לא.