‫الفصل الرابع‬
‫إستقبال مدخلت مؤشر الفأرة


بعد أن قمنا بإستقبال مدخلات لوحة المفاتيح لنقم الآن بإستقبال مدخلات مؤشر الفأرة. و لدينا ثلاثة أشياء نريد‬ معرفتهما حين نستقبل مدخلات الفأرة. فأما الأول فهو مكان المؤشر بالنسبة لمحور السينات و الصادات ‪ X‬و ‪ Y‬و الثاني هو ما‬ إذا كانت حركة المؤشر مجرد حركة فوق النافذة بدون ضغط على أي زر أم أنها نوع من السحب ‪) Drag-Drop‬مثل أن‬ أقوم بالإمساك بكائنٍ ما بمؤشر الفأرة ثم أحركه عدة خطوات ثم أرفع يدي من على الزر كأني ألقيه في المكان الذي حركته‬ إليه). و الأمر الثالث هو أي الأزرار تم ضغطتها و هل رفع المستخدم إصبعه من على الزر أم مازال يضغط. كل تلك الشياء نريد‬ أن نفحصها أثناء اللعبة لكي نتعامل مع المستخدم و نحن في مكتبة جلوت عندنا العديد من الدوال بها العديد من البارامترات التي‬ تظهر لنا هذه الأشياء. و سوف نتدرج في شرح هذه الدوال عن طريق الأمثلة من البسيط إلى الأعقد ..

نظام الإحداثيات:

‫عندما تحدثنا عن دوالة إستقبال مدخلات لوحة المفاتيح مثل ‪ glutKeyboardFunc‬التي كانت تستقبل الأزرار‬‫ التي لها رقم في جدول آسكي قلنا بأنها تستدعي دالة لها ثلاث بارامترات و هي ‪ unsigned char‬ تمرر لها مكتبة جلوت‬‫قيمة الآسكي للزر الذي تم ضغطته و قلنا أيضاً بأنه يوجد في البارامترات متغيران من نوع عدد صحيح يمثلن مكان المؤشر في‬‫ ال‪ X‬و ال‪ .Y‬و لكي نتحدث عن المؤشر ينبغي أن نتحدث عن الإحداثيات. إن ال ‪ X‬و ال‪ Y‬هذه ليست كما نستعملها في مكتبة‬‫الرسوم المفتوحة فمثلًا النقطة 0,0 تعبر عن أعلى يسار الشاشة و ليس عن منتصف الشاشة. إنظر إلى الرسم التوضيحي:



حيث أن الأرقام المكتوبة باللون الأسود تعبر عن الإحداثيات بالبسكل ‪ Pixel‬و هذا النوع من الإحداثيات يستخدم عندما نتعامل مع موضع مؤشر‬ ‫الفأرة على الشاشة فلو كان المؤشر في أسفل النافذة فإن موضعه بالنسبة لمحور الصادات ‪ Y‬هو طول النافذة فهو يختلف حسب‬ ‫طول النافذة و الأرقام التي باللون الأبيض هي للإحداثيات النسبية و التي غلب إستعاملها في مكتبة الرسوم المفتوحة و هي تهمل‬ ‫حجم النافذة و طولها و عرضها فلا يهمها إلا أن تعبر عن المكان بالنسبة للنافذة. و لو أردنا أن نعبر عن إحداثيات منتصف‬ ‫الشاشة في الحداثيات النسبية سنقول (0,0) في كل الحالات مهما كان حجم النافذة أما في نظام الحداثيات بالبكسل إذا أردنا‬ ‫أن نعبر عن منتصف النافذة فيجب أن نضع حجم النافذة في الإعتبار فلو كان عرض النافذة 800 بكسل و طولها 600 بكسل‬ ‫فإن منتصفها سيكون عند النقطة (400,300) و في حالة أنه قد تم تصغير النافذة إلى 200 بكسل في 200 بكسل فإن‬ ‫النقطة (400,300) لم تعد تعبر عن المنتصف بل تعبر عن نقطة خارج النافذة. و في دالة إستقبال مدخلات لوحة المفاتيح‬ ‫التي كان لها بارامتران يعبران عن موضع المؤشر و لكن بإحداثيات المؤشر بالبكسل. تخيل أننا نريد أن نرسم مربعاً في مكان ‬‫المؤشر إذا ضغطنا على زر معين ترى كيف نستنتج الإحداثيات النسبية من الإحداثيات بالبكسل ؟ هناك معادلة للتحويل بين هذين‬ ‫النظامين و لكن نحتاج أولًأ لمعرفة طول و عرض النافذة. طبعاً كنا في الأمثلة التي في بداية الكتاب كنا نستدعي دالة تحديد‬ ‫حجم النافذة ‪ glutInitWindowSize‬و لكننا لم نعد بحاجة إليها لننا دائماً نقوم بعمل glutFullScreen‬ و ‫ليس لنا حاجة في تحديد حجم للنافذة. و لكن لكي نقوم بمعرفة حجم النافذة هناك دالة إسمها ‪ glutReshapeFunc‬ و‬ ‫هي موجودة طبعاً في مكتبة جلوت نقوم بتمرير لها إسم دالة نقوم نحن بإنشائها و مكتبة جلوت تستدعيها حينما يحدث أي تغيير‬ ‫على حجم النافذة و تمرر لنا بارامتران بحجم النافذة (الطول و العرض) و هكذا نعرف حجمها و من ثم نستطيع أن نعمل معادلة‬ ‫أدخل لها البكسل و تخرج لي الموضع بالحداثيات النسبية. إنظر إلى هذا الكود:
int W,H;
void Resize(int Width, int Height)
{
W = Width;
H = Height;
}
هنا قمنا بإنشاء دالة تستقبل عددان صحيحان يعبران عن طول و عرض النافذة و وضعناهم في متغيرات عمومية لنستخدمهم في معادلة التحويل بين نظام الإحداثيات بالبكسل و نظام الإحداثيات النسبية. ثم نقوم في الدالة الرئيسية ‪ main‬بالآتي:‬
glutReshapeFunc(Resize‬‬);
‫حيث ستقوم مكتبة جلوت بإستدعاء تلك الدالة و تمرير لها الحجم الجديد للنافذة ثم في الدالة التي أنشأناها , نضع البارامترات في متغيرات عمومية و هكذا يصبح عندنا متغيرات تحمل طول و عرض النافذة و من ثم نستطيع أن نحول بين نظامي الإحداثيات.‬
‫و الآن جرب أن تقوم بعمل هذا الأمر. و الآن ضع هذه الدالة و ذاك السطر في أي مثال من الأمثلة السابقة و إنظر ماذا سيحدث (لم أطلب منك إستعمال المتغيرات في رسم شئ بعد , فقط ضع الدالة كما هي). و هذا ما سوف تفاجئ به:


المربع الأبيض الذي يحدد مساحة في أسفل يسار النافذة يحدد المساحة التي كانت تعرض قبل تعديل حجم النافذة. أي أن بعد تغيير حجم النافذة ربما صار أقصى يمين النافذة في الإحداثيات النسبية يحمل قيمة 2.5 مثلًا بدلًا من قيمة 1 التي إعتدنا عليها. لقد حدث خطأ في الإحداثيات بسبب أننا أهملنا شئ مهم في دالة تغيير حجم النافذة. عندما كنا لا نقوم بإستدعاء شئ في حالة إذا تغير طول أو عرض النافذة كان القيام بعملية تعديل الحداثيات النسبية يتم تلقائياً بحيث تظل النقطة 0,0 هي نقطة المنتصف و النقطة 1 تعبر عن نهاية النافذة و هكذا. أما و قد قمنا بعمل دالة تستدعى عند تغيير حجم النافذة فإن أمر تعديل‬
الإحداثيات النسبية قد وكل إلينا و يجب علينا أن نقوم به بأنفسنا. إنظر إلى هذا السطر:‬
glViewport(0,0,W,H‬‬);
‫هذا فقط ما تحتاج إليه في الدالة التي تستعدى عند تغيير حجم النافذة لكي تقوم بتعديل الإحداثيات النسبية. أنت هكذا تقوم بإخبار مكتبة الرسوم المفتوحة بالمساحة التي نريد أن نعرضها و طبعاً في هذه الدالة نخبرها بذلك بنظام إحداثيات البكسل لا بنظام الإحداثيات النسبية. و قد قلنا في الدالة أننا نريد عرض المساحة المتراوحة بين المكان 0,0 إلى المكان W,H أي أننا نريد عرض النافذة كلها. و إذا أردنا مثلً عرض مساحة صغيرة من النافذة نقوم مثلًا بكتابة الآتي:‬
glViewport(W/2,H/2,W,H‬‬);
‫و هكذا ستقوم مكتبة الرسوم المفتوحة بعرض ربع النافذة الذي في أسفل يسار الشاشة. و هذه الدالة فقط سنكتبها في الدالة التي تستدعى عند تغيير جحم النافذة. و يمكنك أن تقوم بتجربة هذه الدالة وحدك و تحاول أن تعدل في البارامترات وحدك و ربما أيضاً تقوم بإستعمالها بعد ذلك في عمل عدسة مكبرة لأحد الألعاب أو غير ذلك. و في هذا الكتاب لن أستعمل هذه الدالة مرة آخرى حتى أترك شئ للقارئ و المتعلم لكي يقوم بإكشافه و إستخدامه و حل مشاكله وحده.
إستقبال مدخلت مؤشر الفأرة

‫إلى هنا نكون قد أنهينا الكلام عن الإحداثيات بالقدر الذي نحتاج إليه للتحدث عن إستقبال مدخلات مؤشر الفأرة. عليك أن تعلم بأن لمؤشر الفأرة في مكتبة جلوت ستة دوال لا غنى لك عنهم في برمجة اللعاب. أربعه منهم لإستقبال مدخلاته و إثنان للتحكم فيه. أيضاً لا تنسى أن دوال إستقبال مدخلات لوحة المفاتيح كان بها بارامتران لموضع المؤشر بالبكسل. و لن نقوم بذكر مثال و برنامج على كل دالة بل سنكتفي فقط بذكر الدالة و كيفية التعامل معها إلا إذا إستدعى الأمر عمل مثال من أجل المزيد من التوضيح. و هذا هي إحدى الدوال و أسهلها و أبسطها. إنظر إلى الكود:

void MouseEnter(int SS‬‬)
{
if (SS == GLUT_LEFT‬‬)
‫/* إفعل ما تريد فعله إذا غادر المؤشر النافذة */‬
else if (SS == GLUT_ENTERED‬‬)
/* إفعل ما تريد فعله إذا دخل المؤشر إلى حيز النافذة */
}
‫هكذا قمنا بعمل دالة تقوم بفحص ما إذا كان المؤشر قد دخل إلى حيز النافذة أم خرج. تستقبل هذه الدالة بارامتر واحد من نوع عدد صحيح. فإذا كان هذا المتغير يساوي الثابت ‪ GLUT_LEFT‬فإن المؤشر قد غادر النافذة. و إذا كان المتغير يساوي الثابت ‪ GLUT_ENTERED‬فإن المتغير قد دخل إلى النافذة. و في الدالة الرئيسية ‪ main‬سنخبر مكتبة جلوت بأن تقوم بإستدعاء تلك الدالة إذا ما غادر المؤشر النافذة أو دخل إليها مع تمرير لها متغير من نوع عدد صحيح يعبر عن حالة المؤشر. و إنظر إلى هذا الكود:‬
glutEntryFunc(MouseEnter‬‬);
‫و الآن لندخل إلى الكلام عن دوال أكثر تعقيداً. في الدالة السابقة كنا نستطيع أن نعرف هل المؤشر قد دخل في حيز النافذة أم خرج. و هذه المرة سوف نعرف هل المؤشر قد تحرك و إلى أين تحرك و هل تحرك مع ضغط زر أم تحرك بدون أن يكون هناك زر مضغوط. إنظر إلى هذه الدالة التي أنشأناها:‬
void MouseMove(int x, int y)
{
float Mx = (float)x/W*2-1;
float My = (H-y)/(float)H*2-1;
/* كود */
}
و سوف نقوم بإخبار مكتبة جلوت بأن تقوم بإستدعاء هذه الدالة إذا تحرك المؤشر بدون أن يضغط على أي زر. إنظر الكود:‬
glutPassiveMotionFunc(MouseMove‬‬);
‫و كما ترى قد وضعت لك المعادلة التي ذكرتها التي تأخذ إحداثيات بالبكسل و ترجع لك الحداثيات النسبية. فهذه الدالة تقوم بإستقبال بارامتران يعبران عن مكان المؤشر بالبكسل. و نحن قد أخذناهم و حولناهم إلى إحداثيات نسبية. ربما مثلًا نجعل المتغير ‪ Mx‬و ‪ My‬متغيرات عامة و نقوم في دالة الرسم برسم شكل يشبه للمؤشر. أو ربما نضع صورة (كما سنرى لحقاً في درس الإكساء) حتى يظهر و كأنه مؤشر. يمكنك أن تقوم بوضع تلك الدالة في برنامج و ترسم مثلث حول النقطة التي بها المؤشر لكي يظهر و كأنه مؤشر بالفعل. و ربما تعمل فيه بعض تدريجات اللوان. و سأترك للقارئ الحرية في عمل تلك الفكار حتى يبدع هو بنفسه. أما الدالة الثانية و التي تستدعى حين يضغط المستخدم على أي زر مع تحريك المؤشر فهي لها نفس البارامترات. يمكنك إنشاء دالتان لهذا الأمر. إنظر إلى هذا الكود:
void MouseMove1(int x, int y)
{
Mx = (float)x/W*2-1;
My = (H-y)/(float)H*2- 1;
/* كود */
}
void MouseMove2(int x, int y)
{
Mx = (float)x/W*2-1;
My = (H-y)/(float)H*2- 1;
/* كود */
}
حيث نضع أوامر التعامل مع مؤشر الفأرة إذا كان يتحرك مع ضغط زر في الدالة الأولى و نضع أوامر التعامل معه إذا كان يتحرك مع عدم ضغط أي شئ في الدالة الثانية. و من ثم يجب أن نخبر مكتبة جلوت متى ستستدعي هذه و متى ستستدعي تلك:‬
glutMotionFunc(MouseMove‬‬1);
glutPassiveMotionFunc(MouseMove‬‬2);
و إذا لم يكن هناك فرق في الكود بين التعامل مع الفأرة في كلتا الحالتين يمكن أن قوم عمل دالة واحدة فقط تستقبل مكان المؤشر في بارامتراتها. و تخبر مكتبة جلوت بأن تقوم بإستدعائها في الحالتين
glutMotionFunc(MouseMove);
glutPassiveMotionFunc(MouseMove‬‬);
‫و لندخل إلى الكلام عن دالة آخرى آخرى من دوال إستقبال مدخلات المؤشر. و هي دالة تستدعى عندما يضغط المستخدم‬ ‫على زر من الأزرار و تستدعى مرة آخرى عندما يرفع إصبعه من على الزر. و يتم التمرير للدالة أربع بارامترات كلها أعداد‬ ‫صحيحة. الأول يعبر عن أي الأزرار تم ضغطه (الزر الأيمن أم الأيسر أو الأوسط). و الثاني يقول لك هل المستخدم قد ضغط أم‬ ‫رفع إصبعه. و أما الثالث و الرابع لمكان المؤشر في محور السينات و الصادات ‪ X‬و ‪ Y‬طبعاً بإحداثيات البكسل و سنرى الآن‬ ‫كيف نحولها إلى إحداثيات نسبية قبل أن ندخل إلى شرح الدالة. ينبغي أن تعلم أن هناك ثلاث فروق بين نظامي الإحداثيات.‬ ‫الأول هو في نقطة الأصل (0,0) ففي نظام الإحداثيات النسبية تعتبر نقطة الأصل في تمام منتصف النافذة أما في نظام البكسلات فإن نقطة الأصل في أعلى يسار الشاشة لذلك يجب علينا أن نحرك نقطة الأصل إلى المنتصف. كما أن في نظام البكسلات يزيد المحور‬ ‫‪ Y‬كلما نزلنا إلى الأسفل أما في الإحداثيات النسبية فإن ال‪ Y‬يزيد كلما صعدنا لذلك يجب علينا أن نقلب قيمة ال‪ .Y‬أما الفرق الثالث فهو‬ ‫أن في الحداثيات النسبية تحمل قيمة (1,-1) عند أسفل يسار النافذة أما في البكسلت فإنه يحمل قيمة الطول و العرض. و‬ ‫بإختصار يمكننا أن نضع كل هذا الكلام في معادلة للتحويل بين الإحداثيات النسبية و الإحداثيات بالبكسل. حيث سنعتبر أن قيمة‬ ‫‪ W‬هي عرض النافذة و ‪ H‬هو طولها (و قد شرحنا كيف نعرف قيمة الطول و العرض للنافذة). و ال‪ X‬و ال‪ Y‬يعبران عن‬ ‫مكان المؤشر في إحداثيات البكسل. و سوف نقوم بعمل معادلة لكي تقوم بمعرفة مكان المؤشر بالإحداثيات النسبية (مثلً إذا كنت تريد‬ ‫أن ترسم شئ يمشي مع المؤشر أو تريد أن تضع صورة مؤشر آخرى و تلغي السهم التقليدي).
Mx = (float)X/W*2-1;
My = (H-Y)/(float)H*2-1;
‫و بعد أن تتأمل المعادلتان ستجد الآتي. بالنسبة للمعادلة الأولى التي ترجع لنا قيمة مكان المؤشر في محور السينات في نظام‬ ‫الإحداثيات النسبية عندنا المتغير ‪ X‬يعبر عن مكان المؤشر في محور السينات بالبكسل و المتغير ‪ W‬يعبر عن عرض النافذة‬ ‫أيضاً بالبكسل. و قد إفترضنا أن عرض النافذة في الإحداثيات النسبية هو 2.0 بإفتراض أن المحور عند يسار النافذة و ليس‬ ‫في الوسط لأننا بعد ذلك قمنا بإرجاع المحور إلى الوسط فطرحنا القيمة 1 من الناتج. فبعد أن إفترضنا أن عرض النافذة هو 2‬ ‫قمنا بتطبيق قانون حاصل ضرب الطرفين يساوي حاصل ضرب الوسطين حيث إعتبرنا أن ‪ W‬مقسومة على ‪ X‬تساوي 2 مقسومة‬ ‫على ‪ Mx‬أي النقطة التي نريدها بإعتبار أن النقطة الصفر في اليسار. ثم بعد ذلك طرحنا قيمة 1 حتى نرجع قيمة الصفر‬ ‫البكسل تزيد كلما نزلنا على عكس الإحداثيات النسبية فإنها تقل لذلك قمنا أولً في المعادلة الثانية بقلب قيمتها عن طريق‬ ‫طرح الطول من مكان النقطة.‬
‫و الآن لنتكلم عن الدالة التي ذكرناها و التي تقوم بإستقبال الزر و حالة الضغط و المكان. إنظر إلى الكود التالي:‬
void MouseClick(int Button, int State, int xx, int yy)
{
Mx = (float)X/W*2-1;
My = (H-Y)/(float)H*2-1;
switch (Button)
{
case GLUT_LEFT_BUTTON:
if (State == GLUT_DOWN)
/* كود */
else if (State == GLUT_UP)
/* كود */
break;
case GLUT_MIDDLE_BUTTON:
if (Mx > 0.5 && Mx < 0.8 && My > 0)
/* كود */
}
}
‫ثم لكي تجعل مكتبة جلوت تقوم بإستدعاء هذه الدالة التي أسميناها ‪ MouseClick‬إذا ضغط المستخدم على أي زر نقول:‬
glutMouseFunc(MouseClick);
‫طبعاً كما هو ملاحظ فإن قيمة المتغير الأول و الذي أسميناه ‪ Button‬تعبر عن الزر الذي تم ضغطه. و قيمته تأخذ إحدى‬ ‫الثلاث ثوابت الآتية:‬
‫‪GLUT_LEFT_BUTTON – GLUT_MIDDLE_BUTTON – GLUT_RIGHT_BUTTON
‫أما المتغير الثاني الذي أسميناه ‪ State‬و الذي يعبر عن حالة ضغط الزر يأخذ قيم إحدى الثوابت الآتية:‬
‫‪GLUT_UP – GLUT_DOWN‬‬
‫و لكي نضع النقاط على الحروف علينا أن نستعمل كل ما سبق في مثال للتوضيح. سيقوم هذا البرنامج بعرض مجموعة من الأزرار‬ ‫على الشاشة. و لكل زر لون معين و إذا ضغطت على أي زر فإن لون الخلفية يتحول إلى لون ذلك الزر. كما أن الزر إذا ضغطت‬ ‫عليه يغمق لونه قليلً. ترى هل يمكن أن نفعل كل هذا بإستخدام ما تعلمناه عن التعامل مع المؤشر ؟؟ سنرى هذا في المثال القادم.‬ ‫إنظر إلى الكود:‬
#include <GL/glut.h>
#include <GL/gl.h>
int Slct=1,Dn=0,H=600,W=800;
void DisplayTimer(int V)
{
glutTimerFunc(20,DisplayTimer,0);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
if (Slct == 1)
glColor3f(0,0.5,0);
else if (Dn == 1)
glColor3f(0,0.25,0);
else
glColor3f(1,1,1);
glVertex2f(-0.8,0.8);
glVertex2f(-0.2,0.8);
if (Slct == 1)
glColor3f(1,1,1);
else if (Dn == 1)
glColor3f(0.5,0.5,0.5);
else
glColor3f(0,0.5,0);
glVertex2f(-0.2,0.2);
glVertex2f(-0.8,0.2);
///////////////////////////////////////////////
if (Slct == 2)
glColor3f(1,0.5,0);
else if (Dn == 2)
glColor3f(0.5,0.25,0);
else
glColor3f(1,1,1);
glVertex2f(0.8,0.8);
glVertex2f(0.2,0.8);
if (Slct == 2)
glColor3f(1,1,1);
else if (Dn == 2)
glColor3f(0.5,0.5,0.5);
else
glColor3f(1,0.5,0);
glVertex2f(0.2,0.2);
glVertex2f(0.8,0.2);
///////////////////////////////////////////////
if (Slct == 3)
glColor3f(0.8,0,1);
else if (Dn == 3)
glColor3f(0.4,0,0.5);
else
glColor3f(1,1,1);
glVertex2f(0.8,-0.2);
glVertex2f(0.2,-0.2);
if (Slct == 3)
glColor3f(1,1,1);
else if (Dn == 3)
glColor3f(0.5,0.5,0.5);
else
glColor3f(0.8,0,1);
glVertex2f(0.2,-0.8);
glVertex2f(0.8,-0.8);
///////////////////////////////////////////////
if (Slct == 4)
glColor3f(0,0.8,1);
else if (Dn == 4)
glColor3f(0,0.4,0.5);
else
glColor3f(1,1,1);
glVertex2f(-0.8,-0.2);
glVertex2f(-0.2,-0.2);
if (Slct == 4)
glColor3f(1,1,1);
else if (Dn == 4)
glColor3f(0.5,0.5,0.5);
else
glColor3f(0,0.8,1);
glVertex2f(-0.2,-0.8);
glVertex2f(-0.8,-0.8);
glEnd();
glFlush();
glutSwapBuffers();
}
void Mouse(int Btn, int Sts, int x, int y)
{
float Mx = (float)x/W*2-1;
float My = (H-y)/(float)H*2-1;
if (Btn == GLUT_LEFT_BUTTON)
{
if (Mx > -0.8 && Mx < -0.2 && My > 0.2 && My < 0.8)
{
if (Sts == GLUT_UP)
{
glClearColor(0,0.5,0,1);
Dn = 0;
Slct = 1;
}
else if (Sts == GLUT_DOWN)
Dn = 1;
}
else if (Mx < 0.8 && Mx > 0.2 && My > 0.2 && My < 0.8)
{
if (Sts == GLUT_UP)
{
glClearColor(1,0.5,0,1);
Dn = 0;
Slct = 2;
}
else if (Sts == GLUT_DOWN)
Dn = 2;
}
else if (Mx < 0.8 && Mx > 0.2 && My < -0.2 && My > -0.8)
{
if (Sts == GLUT_UP)
{
glClearColor(0.8,0,1,1);
Dn = 0;
Slct = 3;
}
else if (Sts == GLUT_DOWN)
Dn = 3;
}
else if (Mx > -0.8 && Mx < -0.2 && My < -0.2 && My > -0.8)
{
if (Sts == GLUT_UP)
{
glClearColor(0,0.8,1,1);
Dn = 0;
Slct = 4;
}
else if (Sts == GLUT_DOWN)
Dn = 4;
}
}
}
void Resize(int Width, int Height)
{
W = Width;
H = Height;
glViewport(0,0,W,H);
}
void Disp() {}
int main(int argc, char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800,600);
glutCreateWindow(“Mouse Move Test”);
glClearColor(0,0.5,0,1);
glutMouseFunc(Mouse);
glutTimerFunc(20,DisplayTimer,0);
glutDisplayFunc(Disp);
glutReshapeFunc(Resize);
glutMainLoop();
return 0;
}
‫لا تقلق بشأن طول البرنامج , فسطوره كثيرة في الكم فقط لا في الكيف و كل شئ كتبناه في الكود ذكرناه و شرحناه‬ ‫سابقاً. إن هذا البرنامج يقوم بعرض أربع أزرار مختلفة في الألوان. و إذا تم الضغط على أي زر فإن لون الخلفية يتحول إلى‬ ‫لون ذلك الزر و الزر نفسه يتغير شكله ليظهر و كأنه مضغوط. و عندما نضغط بالفأرة على أي زر فإن لونه يغمق قليلًا حتى‬ ‫نرفع إصبعنا من على الزر اليساري للفأرة. و قد إخترنا أربع ألوان للأربع أزرار و هي اللون 0,0.5,0 للزر الأول و الذي‬ ‫في أعلى يسار النافذة و اللون 0,5.0,1 للزر الثاني الذي في أعلى يمين النافذة و اللون 0.8,0,1 للزر الثالث الذي في‬ ‫أسفل يمين النافذة و اللون 0,0.8,1 للزر الرابع الذي في أسفل يسار النافذة.‬ ‫و نبدأ بشرح البرنامج. في الدالة التي يتم إستدعائها كل 20 ميلي ثانية لكي تقوم بعملية الرسم قمنا برسم الأربع‬ ‫أزرار. و تأمل كيف يرسم الزر الواحد. إنظر إلى هذه القطعة من الكود:‬
if (Slct == 1)
glColor3f(0,0.5,0);
else if (Dn == 1)
glColor3f(0,0.25,0);
else
glColor3f(1,1,1);
glVertex2f(-0.8,0.8);
glVertex2f(-0.2,0.8);
if (Slct == 1)
glColor3f(1,1,1);
else if (Dn == 1)
glColor3f(0.5,0.5,0.5);
else
glColor3f(0,0.5,0);
glVertex2f(-0.2,0.2);
glVertex2f(-0.8,0.2);
إن الزر الواحد يحمل لونين و هما اللون الأبيض و يتدرج بعد ذلك للون الذي يحمله الزر و الذي إذا ضغطت عليه سوف يكسي‬ ‫الخلفية بهذا اللون. فإذا كان الزر مضغوط فإن اللون الأبيض يكون تحت و لون الزر يكون فوق. و إن لم يكن مضغوط و لكن‬ ‫المستخدم مازال يضغط عليه (أي يشير إليه بالمؤشر و لكنه لم يرفع إصبعه من على الزر بعد) فإن الزر سيكون بلون أغمق قليلًا. و قد قمنا بعمل متغيرين عامين في بداية البرنامج و هما ‪ Dn‬و ‪ .Slct‬فأما ‪ Slct‬فيحمل رقم الزر الذي قد ضغط عليه‬ ‫المستخدم. أي رقم الزر المحدد الذي تحمل خلفية النافذة لونه. و أما ‪ Dn‬فيحمل رقم الزر الذي يضغط عليه المستخدم و لم‬ ‫يرفع إصبعه بعد. نحن في البداية نختبر ما إذا كان الزر مضغوط أم لا (لاحظ إختبار قيمة ‪ Stct‬في كل مرة نرسم فيها زر).‬ ‫فإذا كان كذلك قمنا بإختيار لون الزر. و إذا لم يكن مضغوطاً و لكن المستخدم مازال يضغط عليه اللون الأصلي للزر و لكن‬ ‫أغمق قليلًا (اللون الأصلي للزر الأول الذي نشرح كوده الآن هو 0,0,0.5 كما ذكرنا و لكن كما ترى فإننا قد حددنا لون آخر‬ ‫و هو 0,0.25,0 أي أنها نفس النسب و لكن درجة اللون أقل حتى يكون أغمق). و إن كان لا هذا و لا هذا نحدد اللون الأبيض ثم نرسم أول نقطتين (الذان في أعلى الزر). ثم نقوم برسم النقطتين الأخرتين في أسفل الزر. فنختبر قيمة المتغير ‪Slct‬‬ ‫فإذا كان الزر مضغوط نحدد اللون الأبيض. و إن لم يكن مضغوطاً فإننا سوف نختبر قيمة المتغير ‪ Dn‬لكي نعرف ما إذا كان‬ ‫المستخدم مازال يضغط على الزر أم لم يضغط عليه فإذا كان كذلك نقوم بتحديد اللون الرصاصي (يمكن أن تعتبره الأبيض الغامق حيث يستعمل نفس نسب اللون الأبيض و لكن أقل في الدرجة). و إن كان الزر ليس مضغوطاً ولا محدداً فإننا نحدد اللون الأصلي‬ ‫للزر. و هكذا فعلنا مع كل الأزرار بعد ذلك.‬ ‫و يتبقى عندنا شئ واحد ألا و هو كيف نعرف ما إذا كان الزر قد تم ضغطه أم لا و هل المستخدم مازال يضغط أم لا ؟‬ ‫و هذا هو المغزى من المثال. فنحن قد عرفنا أن هناك دالة تستقبل أربع متغيرات من العداد الصحيحة تحمل معلومات عن الزر‬ ‫الذي تم ضغطه و هل المستخدم قد رفع إصبعه أم لا و أين هو المؤشر بإحداثيات البكسل. و نحن قمنا بإنشاء هذه الدالة و‬ ‫أسميناها ‪ Mouse‬ثم أخبرنا مكتبة جلوت بأن تقوم بإستدعائها و ذلك عن طريق هذا السطر:
‪glutMouseFunc(Mouse‬‬);
‫و لنتأمل دالة ‪ Mouse‬التي جعلناها دالة إستقبال مدخلات مؤشر الفأرة. فقد قلنا بأن البارامتر الأول يعبر عن الزر الذي‬ ‫تم ضغطه و نحن نقوم بإختبار قيمته عن طريق الثوابت التي ذكرناها سابقاً. في البداية قمنا بإيجاد مكان المؤشر بالإحداثيات‬ ‫النسبية عن طريق القانون الذي ذكرناه سابقاً و قمنا بحفظ المكان في متغيرين:‬
float Mx = (float)x/W*2-1;
float My = (H-y)/(float)H*2-1;
‫و أما المتغير ‪ H‬و ‪ W‬فهما متغيرات عامة يحملان قيمة طول و عرض النافذة. و قد قمنا بعمل دالة تقوم بأخذ القيمة الجديدة‬ ‫و وضعها في هذين المتغيرين. و هي دالة ‪) Resize‬راجع الكلام عن الحداثيات). بعد ذلك قمنا بإختبار قيمة المتغير ‪Btn‬‬ (الذي كان أول بارامتر في الدالة) لكي نعرف ما هو الزر الذي تم ضغطه. فإذا كان الزر الذي تم ضغطه هو الزر الذي على‬ ‫اليسار نقوم بتنفيذ بقية أوامر الدالة. بعد ذلك نريد أن نعرف المكان الذي تم الضغط فيه على النافذة. أي أننا نريد أن نعرف‬ ‫أي زر تم الضغط عليه أم تم الضغط على الخلفية. قمنا بإختبار مكان ‪ Mx‬و ‪ My‬هل هما في حيز الزر الأول أم لا. و إذا كان لا‬ ‫فهل هما في حيز الزر الثاني أم لا و هكذا. إنظر إلى هذه القطعة من الكود:‬
if (Mx > -0.8 && Mx < -0.2 && My > 0.2 && My < 0.8)
{
if (Sts == GLUT_UP)
{
glClearColor(0,0.5,0,1);
Dn = 0;
Slct = 1;
}
else if (Sts == GLUT_DOWN)
Dn = 1;
}
‫إذا راجعت كود رسم الزر ستجد أننا قد رسمناه في هذا الحيز الذي ذكرناه عندما أردنا إختبار مكان ضغط المؤشر. أي بين القيمة‬ -0.8 و -0.2 في محور السينات و بين 0.2 و 0.8 في محور الصادات. و كما يبدو في الكود أنه إذا كان المؤشر عندما تم‬ ‫الضغط عليه في حيز الزر الأول فإننا نختبر قيمة المتغير ‪) Sts‬البارامتر الثاني الذي يعبر عن حالة الضغط على الزر). فإذا‬ ‫كان المستخدم قد رفع إصبعه من على الزر فإننا نقوم بتغير لون الخلفية لتكون بلون الزر كما ترى عندما إستدعينا دالة تغيير‬ ‫لون الخلفية ‪ glClearColor‬و مررنا لها لون الزر (و البارامتر الرابع في هذه الدالة هو قناة ‪ Alpha‬التي تعبر عن‬ ‫الشفافية دائماً ما نعطيها قيمة 1). و بما أن المستخدم قد رفع إصبعه من على الزر فإننا نجعل قيمة المتغير ‪ Dn‬تساوي صفر‬ ‫لأن المستخدم لم يعد يضغط على أي زر. ثم أعطينا قيمة المتغير ‪ Slct‬القيمة 1 التي تدل على أن الزر المحدد هو الزر الأول‬ ‫و لذلك عندما تأتي دالة الرسم لترسم الزر فإنها سترسم اللون البيض في الأسفل و اللون الأصلي للزر في الأعلى. بعد ذلك‬ ‫كانت العبارة ‪ else‬تأتي إذا لم يكن المستخدم قد رفع إصبعه من على الزر أي أنه مازال يضغط عليه. و حينها سنقوم بإعطاء‬ ‫المتغير ‪ Dn‬قيمة الزر و لذلك عندما تاتي دالة الرسم لترسم الزر فإنها ستجد أن قيمة المتغير ‪ Dn‬تشير إلى الزر الأول‬ ‫لذلك سترسمه بألوان غامقة. و هكذا نكون قد إنتهينا من شرح هذا المثال. و بعد تشغيل البرنامج ستجد أنه بهذا الشكل:

و كما نلاحظ هنا فإن المستخدم قد ضغط على الزر (الثالث) الذي في أسفل يمين الشاشة و أنظر كيف يبدو الزر بلون غامق لأن المستخدم يضغط عليه. و بعد أن رفع المستخدم إصبعه من على الزر إنظر كيف تغير لون الخلفية


و هنا لاحظ كيف تم تغيير لون الخلفية بعد أن ضغطنا على الزر. و لاحظ أن الزر الثالث الذي في أسفل يمين النافذة قد بدا‬ ‫مضغوطاً حيث اللون الأبيض عند أسفله و لونه الأصلي في الأعلى. و لحظ كيف أن الزر الذي بجانبه لم يعد مضغوطاً.‬ ‫و هكذا نكون قد إنتهينا من الكلام عن الدوال التي ترصد تحركات المؤشر و الآن لندخل في الكلم عن دالتان تقومان‬ ‫بالتأثير على المؤشر. أما الدالة الأولى فهي تقوم بتغيير شكل المؤشر. نستدعي هذه الدالة و نقوم بتمرير لها عدد صحيح في‬ ‫شكل إحدى الثوابت المعروفة لكي نقوم بتغيير شكل المؤشر. إنظر إلى هذا السطر:‬
‪glutSetCursor(GLUT_CURSOR_HELP‬‬);
‫مثلًا هذا السطر سيقوم بجعل شكل المؤشر عبارة عن علامة إستفهام و ليس السهم المعتاد. و هناك العديد من الثوابت التي نقوم‬ ‫بتمريرها إلى هذه الدالة لتغيير شكل المؤشر. و لدينا العديد من الأشكال للمؤشر مثل علامة إكس تشير إلى خطأ مثلًا و كذلك‬ ‫السهم و علامة الإستفهام و الساعة الرملية التي تدل على أن هناك عملية يقوم بها البرنامج لذلك عليك الإنتظار و غير ذلك من‬ ‫الأشكال. و كل هذا لا يهمنا في برمجة الألعاب. إنما يهمنا فقط شكل واحد من أشكال المؤشر. إنظر إلى هذا السطر:‬
‪glutSetCursor(GLUT_CURSOR_NONE‬‬);
‫و هكذا سوف يختفي السهم فإذا مر المؤشر فوق النافذة لن تجد شيئاً. و هذا ما نريده في برمجة اللعاب. فمثلً عندما نكون‬ ‫في لعبة من نوع ‪ First Person Shooter‬فإننا لا نريد‬ ‫أن يكون هناك شكل للمؤشر. إذ أن كل شئ يتم بلوحة المفاتيح و دور المؤشر هو في تغيير الزاوية فقط. أما في أي لعبة إستراتيجية ‬نحتاج إلى وجود مؤشر. مثل أن يكون المؤشر على شكل يد إذا مر فوق سلاح مثلًا أو يكون على شكل سكين‬ ‫إذا مر المؤشر فوق جندي من جنود العدو و هكذا. و طبعاً هذه الأشكال للمؤشر لن نجدها في الدالة التي ذكرناها بل سنجدها‬ ‫فقط في حل واحد ألا و هو أن نقوم بإخفاء المؤشر عن طريق هذه الدالة ثم نقوم بعد ذلك برسم الصورة التي نريد مكان‬ ‫المؤشر عن طريق أخذ مكانه بإحداثيات البكسل ثم تحويلها إلى الحداثيات النسبية كما تعلمنا ثم نرسم ما نريد في هذا المكان.
و الآن لنأتي إلى الدالة الأخيرة. و هذه الدالة مهمة جداً في الألعاب الثلاثية الأبعاد و سوف نشرح بعد ذلك ما هي‬ ‫أهميتها. و لكن كل ما عليك أن تعلمه عنها أننا نمرر لها قيمتين من نوع عدد صحيح فتقوم بنقل المؤشر إلى هذا المكان الذي‬ ‫مررناه لها (بإحداثيات البكسل). إنظر إلى هذا الكود:


glutWarpPointer(100,100);
‫حيث مررنا لها مكان المؤشر الذي نريد. فإذا تم إستدعاء هذه الدالة فإن المؤشر سينتقل مباشرةً إلى هذا المكان. فتخيل مثلًا لو تم إستدعاء هذه الدالة داخل دالة التوقيت ماذا سيحدث ؟؟ سوف ينحبس المؤشر في تلك النقطة و لن يخرج.
‫إلى هنا نكون قد إنتهينا من شرح ما يتعلق بمؤشر الفأرة فيما يخصنا في برمجة الألعاب. و نختم هذا الفصل بمثال بسيط نطبق فيه بعض الدوال السابقة. سنقوم بعمل برنامج يجعل المؤشر ينحبس في مربع صغير لا يستطيع أن يخرج منه. كما سنقوم في هذا المثال بإلغاء شكل مؤشر الفأرة و سنقوم برسم مثلث مكانه بالطريقة التي سبق أن وضحناها. و ها هو الكود:‬
#include <GL/glut.h>
#include <GL/gl.h>
float Mx,My;
int H,W;
GLuint Win;
void DisplayTimer(int V)
{
glutTimerFunc(20,DisplayTimer,0);
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_QUADS);
glColor3f(1,0,0.9);
glVertex2f(0.5,0.5);
glVertex2f(0.5,-0.5);
glVertex2f(-0.5,-0.5);
glVertex2f(-0.5,0.5);
glEnd();
glBegin(GL_TRIANGLES);
glColor3f(1,1,1);
glVertex2f(Mx,My);
glColor3f(0,0.6,1);
glVertex2f(Mx+0.1,My);
glVertex2f(Mx,My-0.1);
glEnd();
glFlush();
glutSwapBuffers();
}
void Mouse(int x, int y)
{
Mx = (float)x/W*2-1;
My = (H-y)/(float)H*2- 1;
if (Mx > 0.5 || Mx < -0.5 || My > 0.5 || My < -0.5)
glutWarpPointer(W/2,H/2);
}
void Resize(int Width, int Height)
{
W = Width;
H = Height;
glViewport(0,0,W,H);
}
void Disp() {}
void Keyboard(unsigned char K, int x, int y)
{
if (K == 27)
glutDestroyWindow(Win);
}
int main(int argc, char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowSize(800,600);
Win = glutCreateWindow(“Mouse Move Test”);
glClearColor(0,0.5,0,1);
glutPassiveMotionFunc(Mouse);
glutMotionFunc(Mouse);
glutTimerFunc(20,DisplayTimer,0);
glutSetCursor(GLUT_CURSOR_NONE);
glutDisplayFunc(Disp);
glutKeyboardFunc(Keyboard);
glutReshapeFunc(Resize);
glutMainLoop();
return 0;
}
‫ببساطة قمنا بعمل متغيرين عامين من نوع كسر ‪ float‬ليعبروا عن مكان المؤشر بالإحداثيات النسبية. ثم في دالة‬ ‫الرسم (أو دالة التوقيت) قمنا برسم مربع في الوسط ثم قمنا برسم مثلث عند النقطة ‪ Mx‬و ‪ My‬حتى يبدوا هذا المثلث و‬ ‫كأنه المؤشر. و قمنا في الدالة الرئيسية ‪ main‬بإلغاء شكل المؤشر عن بتمرير ‪ GLUT_CURSOR_NONE إلى‬ ‫الدالة ‪ .glutSetCursor‬ثم قمنا بعمل دالة تقوم مكتبة جلوت بإستدعائها عندما يتحرك المؤشر:‬
‪glutPassiveMotionFunc(Mouse‬‬);
‪glutMotionFunc(Mouse‬‬);
‫ثم قمنا بعد ذلك بأخذ مكان المؤشر في هذه الدالة بإحداثيات البكسل ثم نحولها إلى إحداثيات نسبية و نضعها في المتغيرين‬ ‫العامين ‪ Mx‬و ‪ .My‬و بعد ذلك نقوم بإختبار قيمتهما فإذا تجاوزت حدود المربع فإننا إذاً نقوم بإستدعاء دالة تغيير مكان‬ ‫المؤشر لكي نعيده إلى منتصف النافذة إنظر الكود:‬
‪glutWarpPointer(W/2,H‬‬/2);
|‫حيث قد مررنا لتلك الدالة قيمتين بإحداثيات البكسل. و هما منتصف النافذة بالضبط لننا قد قسمنا الطول و العرض على إثنين‬ ‫حتى نحصل على إحداثيات منتصف النافذة. طبعاً هناك بعض الشياء الآخرى في البرنامج قد تعرضنا لها من قبل مثل عمل دالة‬ ‫‪ Keyboard‬و جعلها تغلق البرنامج إذا ضغط المستخدم ‪ .Escape‬إنظر إلى صورة البرنامج بعد تشغيله:

‫إلى هنا نكون قد إنتهينا من الكلام عن ما يتعلق بنا في التعامل مع مؤشر الفأرة. و كان من أجل أن نفهم كيفية التعامل مع‬ ‫المؤشر كان يجب أن نتكلم عن الإحداثيات. و تذكر أنه كان هناك ستة دوال للتعامل مع المؤشر فضلً عن وجود بارامترات لمكان‬ ‫المؤشر في دوال التعامل مع لوحة المفاتيح. كان عندنا دالة ‪ glutEntryFunc‬التي نمرر لها دالة لها بارامتر واحد من‬ ‫نوع عدد صحيح. و تقوم مكتبة جلوت بإستدعاء تلك الدالة إذا ما خرج المؤشر من أو دخل إلى النافذة. ثم كان عندنا دالة‬ ‫إسمها ‪ glutMotionFunc‬و الدالة ‪ glutPassiveMotionFunc‬حيث نمرر لهما دالة لها بارامتران من نوع‬ ‫عدد صحيح يعبران عن مكان المؤشر بإحداثيات البكسل. و الدالة الأولى تقوم بإستدعاء الدالة التي مررتها لها إذا ما تحرك‬ ‫المؤشر مع ضغط على أحد الأزرار و الثانية إذا ما تحرك المؤشر بدون ضغط على أزرار. و كان عندنا أيضاً دالة أكثر تعقيداً‬ ‫من هاتان الدالتان و إسمها ‪ glutMouseFunc‬نقوم بتمرير لها دالة لها أربع بارامترات من نوع عدد صحيح. فأما الأول‬ ‫فهو يعبر عن الزر الذي تم ضغطه. و الثاني يعبر عن حالة الضغط هل المستخدم مازال يضغط أم رفع إصبعه من على الزر. و أما‬ ‫البارامتر الثالث و الرابع فهما لمكان المؤشر. و كان عندنا دالتان للتحكم في المؤشر. كان عندنا دالة نقوم بتمرير لها رقمان‬ ‫من الأعداد الصحيحة على أنهم إحداثيات بالبكسل فتقوم بنقل المؤشر إلى هذا المكان و إسم الدالة ‪.glutWarpPointer‬‬ ‫و أما الدالة الثانية فهي ‪ glutSetCursor‬نقوم بتمرير لها ثابت من الثوابت يعبر عن شكل المؤشر الذي نريد. و قلنا‬ ‫أننا لن نحتاج في برمجة اللعاب إلى هذه الدالة إلا عندما نريد أن نلغي شكل المؤشر لنضع مكانه ما نريد من أشكال بأنفسنا.
‫و نستطيع القول أننا قد إنتهينا تقريباً من الكلام عن مكتبة جلوت ‪ GLUT‬في هذا الفصل في ما يخصنا في برمجة‬ ‫الألعاب. فلم يكن هدفنا هو أن نشرح تلك المكتبة و لكن أخذنا فقط ما نحتاج إليه في برمجة اللعاب. و يمكنك أن تذهب إلى‬ ‫المرجع الرئيسي لتلك المكتبة حيث ستجد العديد من الدوال و الثوابت التي تحتاجها في عمل أي برنامج بمكتبة جلوت. و في‬ ‫الفصل القادم إن شاء الله سنتحدث بتفصيل أكثر عن مكتبة الرسوم المفتوحة ‪ OpenGL‬و بنظرة عن قرب أكثر إلا أننا‬




‫سنتوقف بعد هذا الفصل لعمل تطبيق على كل ما سبق في لعبة بسيطة من باب إعطاء بعض الأفكار لا أكثر.‬