المقالات العلمية

مقالات الشبكة العربية لمطوري الألعاب

حركة الشخصيات ثلاثية الأبعاد للمبرمجين الجزء الثاني: استخدام مكتبة Cal3D لتحريك الشخصيات

المقدمة

بعد أن ألقينا نظرة فاحصة على مفاهيم حركة الشخصيات في الجزء الأول، سننتقل هنا إلى دراسة إحدى مكتبات حركة الشخصيات التي تطبّق الأساسيات وتحتوي على عدد من الأدوات المفيدة.

لننطلق!

مكتبة Cal3D

Cal3D هي مكتبة++C مفتوحة المصدر تعتمد رخصة LGPL مما يعني إمكانية استخدامها في التطبيقات الغير ربحية وكذلك التطبيقات التجارية من دون أي مقابل مادّي، تقدّم نظام متكامل مستقر لحركة الشخصيات وتعمل على أي نكهة من كل من Windows و Linux و Mac OS.
 Cal3D لن تقوم برسم الشخصيات لك، ولكنها تقدم كافة البيانات المطلوبة لكي يقوم التطبيق بنفسه برسم تلك الشخصيات بالطريقة الأنسب له مما يجعلها مكتبة مستقلة تماماً عن أي محرك أو واجهة برمجية، أي يمكن استخدامها مع DirectX أو OpenGL أو Ogre3D أو محركك الخاص وستظهر النتائج نفسها في كل الحالات.

في الحقيقة، كل ما تحتاجه لبناء المكتبة مترجم ++C يدعم STL، وبالطبع فإن المكتبة تأتي مع ملف مشروع ++Visual C جاهز، وكذلك ملفات Automake/Autoconf للبناء على Linux.

المزايا المهمة المدعومة (حتى الآن):

  • فصل الأجزاء المختلفة التي تشكّل الشخصية وجعلها مستقلة عن بعضها، حيث إن الهيكل العظمي، مجسّم الشخصية، حركة الهيكل العظمي، والخامات Materials المستخدمة لإكساء الشخصية، كل منها يتم حفظه في ملف مستقل عن الآخرين للحصول على مرونة أكبر في التعامل.
  • إمكانية إنشاء أي عدد من الشخصيات استناداً إلى خصائص مشتركة مثل الهيكل العظمي والحركة وأخرى مختلفة مثل الخامات والمجسّم.
  • مصدّرات لعدة برامج تصميم مثل 3D Studio MAX و Maya و Blender.
  • دعم ميزة مزج الحركة التي تتيح مزج تسلسلين حركيين مختلفين أو أكثر بنسب متفاوتة.
  • دعم تجريبي لحركة الملابس باستخدام فيزياء النوابض بالإضافة إلى دعم التعرّف على التصادم بين مجسّم الشخصية والجزء الذي يستخدم فيزياء الملابس لتجنب التداخل بينهما أثناء الحركة.
  • دعم ميزة خفض مستوى الدقة لمجسّم الشخصية أثناء التنفيذ (Progressive Mesh LOD) أي يمكن خفض عدد المضلعات للحصول على أداء أفضل عندما تكون الشخصية بعيدة عن الكاميرا.
  • هيئات ملف متعددة كل منها تصف عنصر من عناصر الشخصية، ولكل منها نمطين: نصّي وثنائي (text and binary)، يستند النمط النصي على XML.
  • نظام إدارة خامات ذو مرونة جيدة (Materials System).
  • الشفرة المصدرية للمكتبة مكتوبة بشكل بسيط ونظيف.
  • هل ذكرت أيضاً إنها مجّانية؟ (ضحكة بلهاء).

بعض العيوب:

  • رغم أن مستوى الأداء الحالي للمكتبة مقبول جدّاً، إلا أن بعض المسائل يمكن كتابتها بطريقة تقدم أداءاً أفضل، مثلاً تحسين إدارة الذاكرة، أو استخدام جدول look-up لتسريع إيجاد الجذر التربيعي.
  • لا توجد طريقة للدخول المباشر على عظمة معينة لتغيير التحويل المطبّق على تلك العظمة يدوياً.
  • لا توجد طريقة للتحكم بنوع الاستيفاء (interpolation) المستخدم للحركة (Linear, Spline, Nearest , ..etc).
  • لا توجد طريقة مباشرة لتحجيم العظام للحصول على أبعاد مختلفة للشخصية، يمكن عمل ذلك الآن ولكن يجب عمله قبل إنشاء نسخة الشخصية CalModel.

معمارية المكتبة

الفكرة الرئيسية التي تستند عليها هذه المكتبة هي فصل البيانات المشتركة بين الشخصيات عن الشخصيات نفسها، وهنالك بيانات مشتركة كثيرة بين الشخصيات على سبيل المثال الهياكل العظمية والحركة. يتم تمثيل كل نوع من الشخصية باستخدام كائن رئيسي يحمل جميع مزايا الشخصية، يسمى هذا الكائن بالنموذج الأساسي Core Model ويستطيع إنشاء أي عدد من الشخصيات التي تمتلك مزاياه كنسخ، تسمّى كل نسخة بنسخة نموذج Model Instance.

لفهم الفكرة بطريقة أفضل، تصوّر أن لدينا لعبة ستحتوي على معركة بين عدد كبير من المحاربين من جهة وعدد كبير من وحوش التنين من جهة أخرى.

سنحتاج إلى اثنين Core Models، واحد للمحاربين والآخر لوحوش التنين، والآن لإضافة محارب جديد يتم إنشاء نسخة من المح��رب عن طريق النموذج الأساسي للمحارب، يمكن أن تمتلك كل نسخة مُحارب بعض المواصفات المختلفة، مثلاً جزء من المحاربين يلبسون دروعاً من الحديد وجزء آخر دروعاً من الخشب، أو مثلاً لون شعر مختلف لكل منهم، أي سيكون الفرق هنا هو الخامة المستخدمة للإكساء، ويمكن أن يمتلك كل منهم مجسّم مختلف أيضاً! نفس الحالة بالنسبة لوحوش التنين.

كما ترى فإن هذا النظام يعطي مرونة كبيرة في تغيير أشكال الشخصيات، وهي ميزة مهمة جدّاً.

لندرك بشكل أدق فائدة هكذا نظام ، لنفرض أن لدينا لعبة كرة قدم، يجب على كل لاعب من الفريقين أن يمتلك مواصفات مختلفة عن اللاعبين الآخرين، والمواصفات المختلفة تتضمن: شكل وجه اللاعب، لون ملابس اللاعب وحذاءه، وكذلك طول اللاعب (يمكنك زيادة أو نقصان الطول عن طريق تحجيم الهيكل العظمي للشخصية قبل إنشاء الـ Model Instance).

أما البقية فهو مشترك بين الفريقين، مجسّم اللاعبين، هيكلهم العظمي، حركتهم. باستخدام نظام Cal3D سنحتاج لإنشاء Core Model للاعب مرة واحدة فقط، ثم إنشاء اللاعبين عن طريقه ولكل منهم مزاياه وحالته المختلفة.

سوف نستعرض الآن معمارية مكتبة Cal3D عن طريق النظر إلى مكونات المكتبة كلّ على حدة:

Core Classes

  • كل صفة من صفات Core Model يديرها Core class واحد أو أكثر، والصفات هي:
  • الهيكل العظمي للشخصية (Skeletons and Bones)
  • بيانات الحركة (مفاتيح التحريك، الحركات، والمسارات)
  • الخامة المستخدمة للإكساء (Materials)
  • أجزاء جسم الشخصية (Meshes and Submeshes)

مخطط UML بسيط يبيّن الأصناف الأساسية وعلاقتها ببعضها البعض:

Instance Classes

كل نسخة من الشخصية يمكن أن تمتلك بيانات مختلفة (ولكن يجب أن يتم تحميل تلك البيانات فعلياً عن طريق Core Model)، هذه البيانات هي كالآتي:

  • الحالة الحالية للهيكل العظمي
  • التسلسلات الحركية الفعّالة
  • أي عدد من المجسّمات الموصولة بالشخصية وهنالك كذلك عدد من المكونات التي تساعد في تبسيط عملية التحكم بالشخصية، وهي كالتالي:
    • مازج الحركة (Mixer)
    • متحكم الجلد (Physique)
    • متحكم حركة الملابس باستخدام نظام نوابض Cal3D
    • واجهة الإظهار (Rendering interface)

العلاقة بين هذه المكونات جميعها يمكن تمثيلها بمخطط UML التالي:

Misc Classes

مكونات أساسية للمكتبة وأخرى مساعدة:

خط معالجة الحركة (Animation Pipeline)

عملية حساب وضع الشخصية النهائي الحالي تستند على البيانات التي يقدمها كل من Core Model ونسخة الشخصية (Model Instance)، تتضمن هذه العملية الخطوات التالي:

  • دمج تأثير جميع السلسلات الحركية الفعّالة للحصول على الوضع النهائي للهيكل العظمي
  • مزج جميع المجسّمات المستخدمة في الحركة التحوّلية (Morphing)
  • حساب تأثير العظام على الرؤوس (vertices) للحصول على المجسّم النهائي
  • محاكاة الملابس لحساب شكلها النهائي لهذا الإطار
  • إرسال البيانات النهائية لواجهة الإظهار "Renderer"

الشكل التالي يمثل تخطيط لمراحل ممر الحركة حتى الحصول على النتائج النهائية:

ملاحظة: مكتبة Cal3D تدعم الحركة التحوّلية ولكن يمكنك اعتبار هذه الميزة تحت التطوير (غير كاملة)، أبسط عقبة ستواجهك هي أن معظم مصدّرات Cal3D لا تدعم تصدير الحركة التحوّلية بعد.

إعداد مكتبة Cal3D للعمل

خطوات إعداد Cal3D للعمل مع التطبيق المطلوب تحت نظام وندوز:

  • تضمين الملف الرئيسي cal3d.h (موجود في المسار: cal3d/src)
  • الربط لمكتبة (cal3d_d.lib(Debug أو (cal3d.lib(Release
  • نسخ المكتبة الديناميكية (cal3d_d.dll (Debug أو (cal3d.dll (Release إلى مسار الملف التنفيذي للتطبيق

 تهانينا! Cal3D الآن طوع يديك...

خطوات إنشاء شخصية جديدة

  • إنشاء الكائن الرئيسي Core Model للشخصية
  • تحميل بيانات الشخصية المخزّنة بالكامل عن طريق Core Model
  • إعداد الخامات التي ستستخدمها الشخصية
  • إنشاء نسخة للشخصية أو أكثر (Model Instances) عن طريق Core Model
  • ربط مجسّم الشخصية المطلوب بالنسخ المنشأة (طبعاً يجب أن يكون المجسّم قد تم تحميله مسبقاً)
  • إعداد مستوى دقة الشخصيات، الخامات المستخدمة للإكساء، الحركة
  • تحديث الشخصيات لكل إطار
  • رسم الشخصيات لكل إطار

عندما ننتهي من استخدام الشخصية يجب التخلص منها بشكل صحيح (مسح كائن الشخصية)، كذلك الأمر بالنسبة للـ Core Model.

هيئة ملفات Cal3D

  • وصف الهيكل العظمي، الامتداد: csf/ xsf
  • معلومات سلسلة حركية معينة، الامتداد: caf/xaf (لكل حركة ملف، مثلاً للركض ملف وللمشي ملف آخر)
  • المجسّم، الامتداد: cmf/xmf (إن كانت الشخصية تتألف من عدة أجزاء، كل منها سيكون في ملف منفصل)
  • الخامات، الإمتداد: crf/xrf (لكل خامة ملف)
  • ملف عرض الشخصية، الإمتداد: cfg (يمكن استخدامه لعرض الشخصية باستخدام إحدى إصدارات cal3d_miniviewer عن طريق سحب وإسقاط هذا الملف على أيقونة الملف التنفيذي لبرنامج cal3d_miniviewer)

دليل البرمجة باستخدام Cal3D

سوف نأخذ كل مكوّن من المكونات الأساسية ونستعرض العمليات الأساسية التي يمكن القيام بها عن طريقه.

Core Model

وهو أساس الشخصيات في Cal3D، وهو المسؤول عن تحميل كافة البيانات للشخصية وإنشاء الشخصيات فعليّاً، نحتاج Core Model واحد لكل نوع من الشخصيات.

إنشاء Core Model

لإنشاء الكائن CalCoreModel، يجب استخدام المشيّد الخاص بالكائن مع تمرير اسم مميز، مثال:

CalCoreModel myCoreModel("Zombies");

تحميل الهيكل العظمي

يتم تحميل جميع البيانات المطلوبة للشخصية عن طريق Core Model، لتحميل الهيكل العظمي نستخدم الإجراء CalCoreModel::loadCoreSkeleton، مثال:

if(!myCoreModel.loadCoreSkeleton("zombie.csf"))
{
 // التعامل مع الخطأ
}

تحميل السلاسل الحركية

نستطيع استخدام الإجراء CalCoreModel::loadCoreAnimation، والذي يعيد رقماً مميزاًَ للحركة نستخدمه للتحكم بالحركة في ما بعد (لتشغيلها أو أيقافها)، مثال:

int idleAnimationId, walkAnimationId;
idleAnimationId = myCoreModel.loadCoreAnimation("zombie_idle.caf");
walkAnimationId = myCoreModel.loadCoreAnimation("zombie_walk.caf");
if(idleAnimationId == -1 || walkAnimationId == -1)
{
 // التعامل مع الخطأ
}

تحميل المجسّمات

نستخدم الإجراء CalCoreModel::loadCoreMesh لتحميل المجسّمات والذي يعيد رقماً مميزاً للمجسّم نستخدمه للتحكم في ما بعد، مثال:

int bodyId, helmetId;
bodyId = myCoreModel.loadCoreMesh("hero_body.cmf");
helmetId = myCoreModel.loadCoreMesh("hero_helmet.cmf");
if(bodyId == -1 || helmetId == -1)
{
    //  التعامل مع الخطأ
}

تحميل ملفات تعريف الخامات

نستخدم الإجراء CalCoreModel::loadCoreMaterial والذي يعيد رقماً مميزاً للخامة نستخدمه للتحكم بالخامة في ما بعد، مثال:

int upperBodyId, lowerBodyId;
upperBodyId = myCoreModel.loadCoreMaterial("zombie_upper_body.crf");
lowerBodyId = myCoreModel.loadCoreMaterial("zombie_lower_body.crf");
if(upperBodyId == -1 || lowerBodyId == -1)
{
 // التعامل مع الخطأ
}

إطلاق سراح CalCoreModel

عندما لا نحتاج النموذج الأساسي بعد الآن (مثلاً في نهاية التطبيق) كل ما علينا فعله هو التسبب باستدعاء مدمّر الكائن CalCoreModel (class destructor).

ذلك يعني إن قمنا بإنشاء الكائن على الذاكرة الديناميكية أي باستخدام new يجب تطبيق delete عليه في النهاية بطبيعة الحال.

نظام الخامات Materials System

حسب نوع الشخصية والمزايا المطلوبة، نحتاج القيام بعدد من الخطوات لكي يعمل نظام الخامات بشكل صحيح.

من أهم تلك الخطوات، تحميل الإكساءات (من ملفات الصور مثلاً) وإعدادها للاستخدام، حيث إن مكتبة Cal3D لن تقوم بتحميل الإكساءات بل ستترك لنا هذه المهمة، لأن ذلك يقع خارج نطاق عملها وكذلك بسبب الطرق المختلفة التي نستطيع بواسطتها إدارة والتعامل مع الإكساءات، ولكن توفر لنا Cal3D نظاماً ذو مرونة مقبولة لتسهيل العملية.

يمكن لأي خامة أن تمتلك عدداً من الخرائط (maps)، كل خريطة تحوي وصفاً لإكساء، يتم تخزين معرّف من قبل المصدّر مع كل خريطة، تلك القيمة هي عادة اسم ملف الإكساء وذلك ما سنستخدمه لتحميل الإكساءات. نستطيع تخزين الإكساء الذي حمّلناه بأيدينا داخل نظام الخامات والعودة له فيما بعد، باستخدام ميزة تخزين بيانات المستخدم (User data storage) داخل نظام الخامات.

الإجراءات المفيدة لعمليات الخامات:

  • (CalCoreModel::getCoreMaterial (int coreMaterialId: تعيد مؤشر لخامة عن طريق الرقم المعرّف لها.
  • ()CalCoreModel::getCoreMaterialCount: تعيد عدد الخامات الكلّي.
  • ()CalCoreMaterial::getMapCount: تعيد عدد الخرائط الكلّي في الخامة.
  • (CalCoreMaterial::getMapFilename (int mapId: تعيد اسم ملف الإكساء للخريطة عن طريق الرقم المعرّف لها.
  • (CalCoreMaterial::setMapUserData (int mapId, Cal::UserData userData: تخزين بياناتنا الخاصة داخل الخريطة المحددة بالمعرّف عن طريق ميزة بيانات المستخدم.
  • (CalCoreMaterial::getMapUserData (int mapId: نسترجع بياناتنا المخزّنة داخل الخريطة المحددة بالمعرّف.

مثال يبين خطوات تحميل الإكساء:

// تحميل جميع الإكساءات في الخامات وتخزينها كبيانات مستخدم داخل الخرائط
int matId;
for(matId = 0; matId < myCoreModel.getCoreMaterialCount(); matId++)
{
 // استرجاع الخامة الحالية
 CalCoreMaterial *pCoreMaterial;
 pCoreMaterial = myCoreModel.getCoreMaterial(matId);
 // والآن لنقم بزيارة جميع الخرائط وتحميل الإكساءات لها
 // لاحظ أنه يمكن لأي خامة ألا تمتلك أي خريطة (بدون إكساء)
 int mapId;
 for(mapId = 0; mapId < pCoreMaterial->getMapCount(); mapId++)
 {
  // استرجاع اسم ملف الإكساء
  std::string strFilename;
  strFilename = pCoreMaterial->getMapFilename(mapId);
  
  Cal::UserData textureId;
  // استخدام إجراء التحميل الخاص بنا
  textureId = (Cal::UserData)myTextureLoadingFunction(strFilename);
  // تخزين معرّف الإكساء داخل الخريطة الحالية
  pCoreMaterial->setMapUserData(mapId, textureId);
 }
}

يقدم Cal3D نظاماً جيداً لإدارة وتوزيع الخامات المستخدمة في الشخصية، لكي نستخدمه يجب تعريف thread لكل خامة، ثم إضافة جميع الـ threads إلى مجموعة معينة (set).

لا شيء أفضل من مثال لفهم الفكرة! لنفترض أن لدينا محارب من المحاربين الأشدّاء، يستطيع هذا المحارب أن يحمل درعاً وخوذة من الفولاذ أو درعاً وخوذة من الخشب، والفرق بين الاثنين هو الخامة المستخدمة (والوزن والقوة المضافة طبعاً ولكن ذلك خاص باللعبة)، لتحقيق هذا نعرّف اثنان من الـ threads أحدهما للدرع والآخر للخوذة.

ثم نقوم بتعريف مجموعتين (sets) من الخامات، كل منهما يحمل خامة مختلفة، والآن عند التنفيذ يمكننا التبديل بين الاثنين عن طريق تغيير مجموعة الخامات!

// threadsنعرّف اثنان من الـ 
const int SHIELD_MATERIAL_THREAD = 0;
const int HELMET_MATERIAL_THREAD = 1;
myCoreModel.createCoreMaterialThread(SHIELD_MATERIAL_THREAD);
myCoreModel.createCoreMaterialThread(HELMET_MATERIAL_THREAD);
// والآن سنعرّف مجموعتان كل منهما تحوي خامات مختلفة
// بافتراض إن الخامات قد تم تحميلها مسبقاً والحصول على معرّفاتها
const int PLATE_MATERIAL_SET = 0;
const int WOOD_MATERIAL_SET = 1;
myCoreModel.setCoreMaterialId(SHIELD_MATERIAL_THREAD, PLATE_MATERIAL_SET,shieldPlateMaterialId);
myCoreModel.setCoreMaterialId(SHIELD_MATERIAL_THREAD, WOOD_MATERIAL_SET,shieldWoodMaterialId);
myCoreModel.setCoreMaterialId(HELMET_MATERIAL_THREAD, PLATE_MATERIAL_SET,helmetPlateMaterialId);
myCoreModel.setCoreMaterialId(HELMET_MATERIAL_THREAD, WOOD_MATERIAL_SET,helmetWoodMaterialId);
// كل شيء جاهز الآن!

نسخ الشخصيات Model Instance

كما ذكرنا، بعد إعداد النموذج الأساسي للشخصية المطلوبة، يمكن إنشاء أي عدد من النسخ من تلك الشخصية للاستخدام الفعلي داخل اللعبة، كل منها يمثل بالكائن CalModel، وكل منها يمتلك حالته الخاصة، والتي تتضمن: المجسّمات الموصولة له، الحركة الفعّالة، ومستوى دقة العرض (Level-of-Detail).

إنشاء نسخة من الشخصية

يتم إنشاء كائن نسخة الشخصية CalModel باستخدام كائن CalCoreModel معد بالكامل مسبقاً، مثال:

CalModel myCharacter(&myCoreModel);

وصل المجسّمات بالشخصية

عندما تنشئ الشخصية، لا تمتلك تلك الشخصية أي مجسّمات، لذلك فيجب وصل المجسّمات المطلوبة لها والتي من أهمها طبعاً جسم الشخصية نفسه، ولكن يجب أن تكون تلك المجسّمات محمّلة مسبقاً داخل Core Model الشخصية.

نستخدم الإجراء CalModel::attachMesh للوصل، بهذا الشكل:

myCharacter.attachMesh(warriorBodyMeshId);
myCharacter.attachMesh(warriorHelmetMeshId);

وبالطبع كما يمكننا الوصل، يمكننا فصل مجسّم عن الشخصية:

myCharacter.detachMesh(warriorHelmetMeshId);

التحكم بمستوى دقة الشخصية (Level-of-Detail)

يمكن استخدام الإجراء CalModel::setLodLevel لضبط مستوى دقة الشخصية، يأخذ هذا الإجراء قيمة حقيقية تتراوح بين 0.0 و 1.0 والتي تعرّف عدد المضلعات الواجب استخدامه لعرض الشخصية، قيمة 0.0 تعني التخلص من جميع الوجوه التي يمكن التخلص منها، والقيمة 1.0 تعني إن الوجوه ستعرض بأعلى دقة ممكنة أي عدد الوجوه نفسه الذي تم تخزينه كمجسّم.

يجب التنبه لملاحظة، القيمة 0.0 لا تعني أن عدد الوجوه يصبح صفراً ولكن ببساطة أن الشخصية بأقل عدد من الوجوه الممكن لها.
مثال يبين كيفية القيام بالعملية:

myCharacter.setLodLevel(0.5f); // مستوى دقة 50% لنسخة الشخصية هذه

تنبيه مهم/ عملية تغيير مستوى دقة الشخصية عملية مكلفة حسابياً وغير مناسبة للاستدعاء كل إطار، تجنّب تغيير الدقة باستمرار وإلا فإن الأداء سينخفض بشكل كبير، بدل ذلك حاول تغيير دقة عرض الشخصية على مراحل متباعدة قدر الإمكان.

التحكم بالخامة

يمكنك تغيير مجموعة الخامات التي تستخدمها الشخصية عن طريق الإجراء CalModel::setMaterialSet، ولكن يجب أن تكون تلك الخامات معرّفة مسبقاً داخل نظام الخامات، مثال:

myCharacter.setMaterialSet(WOOD_MATERIAL_SET);

كذلك يمكنك تغيير مجموعة الخامات التي يستخدمها مجسّم معين بدل الشخصية بكاملها عن طريق الإجراء CalMesh::setMaterialSet، على سبيل المثال لتغيير خوذة الشخصية فقط:

myCharacter.getMesh(warriorHelmetMeshId)->setMaterialSet(PLATE_MATERIAL_SET));

التحكم بحركة الشخصية

هنالك نوعان من الحركة يمكن تشغيلهما باستخدام الكائن المسؤول عن الحركة CalMixer الموجود داخل كل نسخة شخصية: • الحركة التكرارية (cycles) وهي حركة تستمر وتتكرر طول الوقت.

الحركة مرة واحدة (Actions) وهي حركة يتم تشغيلها مرة واحدة فقط ثم تتوقف بعدها بشكل ذاتي

لاحظ أن جميع السلاسل الحركية يمكن تشغيلها باستخدام أي من النوعين.

لنبدأ بالحركة التكرارية (Cycles)، هنالك إجرائين مهمين نستخدمهما للتحكم بالحركة:

bool CalMixer::blendCycle(
    int id,
    float weight,
    float delay);

يقوم هذا الإجراء بتشغيل حركة تكرارية معينة، البارمترات هي كالآتي:

  • id: معرّف الحركة المطلوب تشغيلها
  • weight: مقدار تأثير الحركة المطلوب (وزن الحركة)
  • delay: الوقت بالثواني الذي تحتاجه الحركة حتى تصل لأقصى وزن لها، نستخدم هذه الميزة لكي ننتقل من حركة إلى أخرى بطريقة ناعمة سلسة بدل أن يكون الانتقال مفاجئ وغير واقعي.

أما الإجراء الثاني:

bool CalMixer::clearCycle(int id, float delay);

يقوم هذا الإجراء بإطفاء حركة تكرارية فعّالة الآن، الحركات التكرارية لن تتوقف لوحدها، البارمترات: id: معرّف الحركة المطلوب تشغيلها
delay: الزمن بالثواني حتى يصبح تأثير الحركة صفراً. على سبيل المثال لو كانت لدينا شخصية محارب تمشي الآن، وأردنا جعلها تنتقل للركض بشكل تدريجي خلال ثانية واحدة، يجب إطفاء حركة المشي وجعل فترة الإطفاء تستغرق ثانية واحدة، وتشغيل حركة الركض بوزن كامل وجعل الحركة تتدرج خلال ثانية واحدة، هكذا:

myWarrior.getMixer()->clearCycle(walkAnimationId, 1.0f);
myWarrior.getMixer()->blendCycle(runAnimationId, 1.0f, 1.0f);

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

mySoldier.getMixer()->blendCycle(walkAnimationId, 0.7f, 0.25f);
mySoldier.getMixer()->blendCycle(limpAnimationId, 0.3f, 0.25f);

وذلك كل ما تحتاج! يمكننا أيضاً إعادة حركة المشي الطبيعية بالتدريج عندما يستخدم الجندي عدة طبية لعلاج قدمه المصابة مثلاً..

والآن لنأتي للحركة مرة واحدة (Actions)، كما ذكرنا يقوم Cal3D بتشغيل هذه الحركة مرة واحدة ثم إيقافها بالكامل، هنالك إجرائين يستخدمان للتحكم بالحركة المطلوبة:

bool CalMixer::executeAction( 
    int id,
    float delayIn,
    float delayOut,
    float weightTarget=1.0f,
    bool autoLock=false);

يقوم هذا الإجراء بتشغيل حركة معينة مرة واحدة، البارامترات كالآتي:

  • id: معرّف الحركة المطلوبة
  • delayIn: الوقت بالثواني الذي تستغرقه الحركة للوصول لأقصى وزن لها منذ بداية التنفيذ
  • delayOut: الوقت بالثواني الذي تستغرقه الحركة حتى تصل للوزن صفر منذ لحظة انتهاء الحركة
  • weightTarget: وزن المزج الأقصى المطلوب
  • autoLock: إذا قمت بتمرير true لهذا البارمتر، فعند نهاية الحركة مرة واحدة، سوف تتوقف العظام المتحركة كما هي حتى تستدعي بنفسك الإجراء removeAction، هذا البارمتر يستحق وقفة لفهم أهميته.

لنأخذ مثالاً، لنفترض إن لدينا محارب نود أن يقوم برفع سيفه والإبقاء عليه مرفوعاً أثناء المشي (مستعد لمواجهة العدو)، ما يجب أن تقوم به هو تعريف حركة تنطبق على الذراع فقط، تجعلها ترتفع، ثم تشغّل هذه الحركة كحركة مرة واحدة (Action)، ولكن ما الذي سيحصل عندما تنتهي حركة الذراع تلك؟ إن قمت بتمرير false لـ autoLock سوف تنتهي الحركة وتعود الذراع إلى مكانها الطبيعي من حركة المشي، ولكن نحن نريد أن تبقى الذراع مرفوعة! وذلك ما سيحصل عندما تمرر true لـ autoLock، حيث ستتوقف الذراع على حالها لحظة انتهاء الحركة، أي إن موقعها سيتم إقفاله، لكي تعيد تفعيل الذراع يجب أن تقوم بنفسك باستدعاء الإجراء removeAction. bool removeAction(int id);

يقوم هذا الإجراء بإيقاف حركة من نوع Action تم تشغيلها مسبقاً، تأخذ بارمتراً واحداً فقط وهو معرّف الحركة التي يجب إيقافها.
السؤال الذي يطرح نفسه الآن، لماذا نحتاج الحركة مرة واحدة؟

بالأساس تُستخدم لتنفيذ حركات تنطبق على جزء واحد من جسم الشخصية، مثلاً حركة تلويح باليد، أو مثلاً فتح باب، نود أن يتم تشغيل هذا النوع من الحركة مرة واحد، مثلاً:

myCharacter.getMixer()->executeAction(waveAnimationId, 0.3f, 0.3f);

تحديث نسخة الشخصية

يجب علينا أن نقوم بتحديث الشخصية كل إطار مع تمرير فرق الزمن بين الإطار الحالي والإطار الذي سبقه كقيمة (float (time delta حيث 1.0f تعني ثانية واحدة، بهذا الشكل:

myCharacter.update(fTimeDelta);

مما يعطيني فكرة، يمكن استخدام هذه القيمة لعمل مؤثرات من قبيل تبطيء الحركة عن طريق قسمة فرق الزمن الفعلي على معامل ما!
وكذلك عدم تمرير قيمة فرق زمن لجعل الشخصية تتوقف تماماً بشكلها الحالي (pause).

إطلاق سراح نسخة الشخصية

كما في كل شيء، عندما ننتهي من نسخة شخصية معينة، يجب علينا إطلاق سراحها، كل ما علينا فعله لعمل ذلك هو التسبب باستدعاء مدمّر كائن (CalModel (class destructor.

ذلك يعني إن قمنا بإنشاء الكائن على الذاكرة الديناميكية أي باستخدام new يجب تطبيق delete عليه في النهاية.

كيف نرسم شخصيات Cal3D؟

كما ذكرت في البداية، Cal3D لن يقوم برسم الشخصيات فعلياً لنا لأنه مستقل تماماً عن أي واجهة أو مكتبة رسم قد نستخدمها، ولكنه يعطي نظاماً مساعداً للرسم لا يترك لنا الكثير لعمله.

يمكن رسم الشخصيات بإحدى طريقتين، الأولى باستخدام ممر الرسوميات الثابت (Fixed Function Pipeline) وتتم عن طريق استخدام الكائن CalRenderer الذي يمكن الحصول عليه عن طريق نسخة الشخصية (أي الكائن CalModel)، يقوم Cal3D بنفسه بتطبيق معادلات تفاعل مجسّمات الشخصية مع العظام (الجلد) ثم يناولنا بيانات جاهزة على طبق من فضة.

أما الطريقة الثانية، فهي باستخدام ممر الرسوميات القابل للبرمجة (Programmable Pipeline)، يجب هنا إنشاء كائن متخصص بالمهمة وهو CalHardwareModel، من المهم الانتباه إلى أنه ليس بديلاً لـ CalModel، ولكنه كائن مستقل مهمته أخذ البيانات التي يقدمها CalModel وتجهيزها وترتيبها بحيث يمكن تمريرها إلى مظلل الرؤوس المستخدم، ولكن في هذه الحالة يجب على المظلل (أو كاتب المظلل بشكل أدق) أن يقوم بعملية حساب تأثير العظام على الرؤوس بنفسه، إضافة بالطبع إلى التحويل والإضاءة.

سوف أشرح خطوات الرسم العامة باستخدام ممر الرسوميات الثابت (تنطبق الخطوات على OpenGL و Direct3D بنفس الطريقة تماماُ)، ثم في النهاية سأقدم تطبيقين لـ Direct3D أحدهما يستخدم ممر الرسوميات الثابت والآخر يستخدم ممر الرسوميات القابل للبرمجة عن طريق الكائن CalHardwareModel، وطبعاً لا أحد غير فنسنت سيتم استخدامه لهذه التجربة البريئة.

أود أن أبشّر محبّي OpenGL أن هنالك مثالين لـ OpenGL أحدهما يستخدم ممر الرسوميات الثابت والآخر يستخدم ممر الرسوميات القابل للبرمجة موجودان مع ملفات مكتبة Cal3D نفسها (تحديداً cal3d_miniviewer_gl و cal3d_miniviewer_gl_vp)، حيث أنهما ممتازان كنقطة بداية.

حسناً، لرسم الشخصية باستخدام ممر الرسوميات الثابت في محركك أو لعبتك، هنالك عدة ملاحظات مهمة:

  • نستخدم الكائن CalRenderer المسؤول عن تمرير معلومات الرسم.
  • يجب أن تتم عملية الرسم داخل زوج من CalRenderer::beginRendering() و CalRenderer::endRendering().
  • الفكرة الأساسية لعملية رسم الشخصية هي زيارة جميع مجسّماتها ثم رسم جميع المجسّمات الفرعية التي نجدها (submeshes).
  • ما دمنا سنستخدم ممر الرسوميات الثابت، يجب على أقل تقدير أن نعيد كتابة كل الرؤوس الجديدة (vertices) إلى ذاكرة الرؤوس للشخصية كل إطار! وذلك لأن عملية تحريك الشخصية بواسطة العظام (الجلد) التي تحصل داخل Cal3D سوف تُعدّل موقع كل رأس استجابة لتأثير العظام لكل إطار، كذلك تعديل كل ناظم للحصول على إضاءة صحيح، قد يبدو ذلك مبالغاً فيه ولكن لا مهرب من ذلك، من الأفضل إذن تحضير ذاكرتنا المستخدمة للرؤوس للتغيّر المستمر (في حالة Direct3D، استخدم D3DUSAGE_DYNAMIC عند إنشاء ذاكرة الرؤوس).

لو اخترنا استخدام ممر الرسوميات القابل للبرمجة، لن نحتاج CalRenderer، وسوف نحتاج كتابة كل شيء مرة واحدة فقط، وعند الرسم سوف يتحمل مظلل الرؤوس مسؤولية تعديل مواقع الرؤوس حسب العظام بنفسه، قد يعطيك هذا فكرة حول سبب كون بطاقات الرسوميات القابلة للبرمجة تمثل ثورة في عالم الرسوميات.

لاسترجاع البيانات الهندسية للمجسّم والتي تضم: الرؤوس، النواظم، المضلعات، إحداثيات الإكساء. يمكن استخدام مجموعة إجراءات تستطيع جميعها ملئ الذاكرة التي تم تمريرها لها بالبيانات دون تدخلنا.

على سبيل المثال، في Direct3D لو قمنا بتمرير مؤشر لمخزن رؤوس مقفل (locked vertex buffer) إلى إجراء getVertices، سيقوم الإجراء بملء ذاكرة الرؤوس بشكل صحيح ثم إعادة عدد الرؤوس التي قد تم كتابتها فعلياً للذاكرة، كذلك الأمر بالنسبة للإجراءات الأخرى.

إجراءات الرسم لاسترجاع البيانات هي كالتالي:

  • ()CalRenderer::getVertices
  • ()CalRenderer::getNormals
  • ()CalRenderer::getTextureCoordinates
  • ()CalRenderer::getFaces

كذلك هنالك إجراءان مفيدان جداً يملئان لنا بيانات مُركَّبة، أي يمكن لكل منهما ملء الرؤوس بالكامل:

  • ()CalRenderer::getVerticesAndNormals
  • ()CalRenderer::getVerticesNormalsAndTexCoords

المثال القادم يبين عملية الرسم خطوة بخطوة بدون الدخول في تفاصيل واجهة أو محرّك رسوم محدّد، يمكن أن تتغير بعض النواحي عند استخدام واجهات رسوم معينة، مثلاً في حالة Direct3D من الأفضل استخدام مخزن رؤوس (vertex buffer) ومخزن فهارس (index buffer) لاسترجاع البيانات بدل الذاكرة الاعتيادية:

// نحصل على مؤشر لراسم الشخصية
CalRenderer *pCalRenderer = myCharacter.getRenderer();
// نبدأ عملية الرسم
if(!pCalRenderer->beginRendering())
{
    // التعامل مع الخطأ
}
// نقوم بضبط إعدادات واجهة الرسوم الخاصة بنا هنا استعدادا للرسم
// نحصل على عدد المجسّمات الموصولة للشخصية
const int meshCount = pCalRenderer->getMeshCount();
// نزور جميع مجسّمات الشخصية بالتسلسل
for(int meshId = 0; meshId < meshCount; meshId++)
{
    // نحصل على عدد المجسّمات الفرعية للمجسّم الحالي
    const int submeshCount = pCalRenderer->getSubmeshCount(meshId);
    // نزور كل مجسّم فرعي لنرسمه
    for(int submeshId = 0; submeshId < submeshCount; submeshId++)
    {
    // نقوم بتحديد المجسّم والمجسّم الفرعي الحالي لاسترجاع البيانات
    if(pCalRenderer->selectMeshSubmesh(meshId, submeshId))
    {
        // نحصل على بيانات الخامة للمجسّم الفرعي الحالي
        unsigned char ambientColor[4], diffuseColor[4], specularColor[4];
        pCalRenderer->getAmbientColor(&ambientColor[0]);
        pCalRenderer->getDiffuseColor(&diffuseColor[0]);
        pCalRenderer->getSpecularColor(&specularColor[0]);
        float shininess = pCalRenderer->getShininess();
   
        // نسترجع بيانات الرؤوس التي تم تحويلها من قبل المكتبة
        // (ولكن تم عمل الموضوع هكذا للبساطة heap(من الأفضل حجز الذاكرة من
        static float meshVertices[10000][3];
        int vertexCount = pCalRenderer->getVertices(&meshVertices[0][0]);
        // نسترجع بيانات النواظم التي تم تحويلها من قبل المكتبة
        static float meshNormals[10000][3];
        pCalRenderer->getNormals(&meshNormals[0][0]);
        // نسترجع بيانات الإكساء للمجسّم فقط لطبقة الإكساء الأولى لتبسيط الأمور
        // في تطبيق حقيقي، يمكن كتابة بيانات الإكساء مرة واحدة، لأنها لا تتغير
        static float meshTextureCoordinates[10000][2];
        int textureCoordCount = pCalRenderer->getTextureCoordinates(0,
        &meshTextureCoordinates[0][0]);
   
        // لنحصل على معرّف الإكساء الذي قمنا بتخزينه عند تحميل الإكساء
        Cal::UserData textureId = pCalRenderer->getMapUserData(0);
        //إعداد الخامة المستخدمة، بيانات المجسّم، وحالات الإكساء في واجهة الرسوميات
   
        // نسترجع بيانات الرؤوس (الفهارس) لاحظ إننا نستخدم فهارس بحجم 32 بت
        // في تطبيق حقيقي، يمكن كتابة بيانات الوجوه مرة واحدة فقط، لأنها لا تتغير!
        static int meshFaces[30000][3];
        int faceCount = pCalRenderer->getFaces(&meshFaces[0][0]);  
   
        // يمكنك الآن أن تقوم بعملية الرسم بواسطة واجهة الرسوميات
    }
    }
}

تطبيق تحميل وتحريك فنسنت (FFP)

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

لو قارنت كود تحميل ورسم مجسّمات الشخصية بين هذا التطبيق والكود السابق الذي يبين طريقة الرسم بشكل عام، ستجد أن التطبيق يقوم بكتابة بيانات الفهارس مرة واحدة فقط وليس كل إطار (ضمن الإجراء SetupGeometry) ولكن ذلك قد عقد ميكانيكية الرسم قليلاً كما ستلاحظ، بينما ليس هنالك مهرب من إعادة كتابة الرؤوس والنواظم كل إطار.

كذلك فإن حجم ذاكرة الرؤوس المستخدمة تم احتسابها لتكون بحجم أكبر مجسّم فرعي ضمن الشخصية، وذلك هو أقصى حجم نحتاجه. أما بالنسبة لذاكرة الفهارس فالحجم يساوي مجموع عدد الوجوه في الشخصية بكاملها مضروباً في 3 (لأن كل وجه يتكون من ثلاثة فهارس).

تطبيق تحميل وتحريك فنسنت (PP):

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

يستخدم هذا المثال الكائن CalHardwareModel الذي يوفره Cal3D والمختص بتقديم خدمات الرسم عند استخدام ممر الرسوميات القابل للبرمجة، إن ألقيت نظرة على الكود ستلاحظ مباشرة أنه أطول وأعقد قليلاً، بالنسبة للمظلل فقد قمت بكتابته من الصفر بدل استخدام المظلل المرفق مع Cal3D، لسببين، الأول هو لتسهيل عملية قراءة وفهم كود المظلل، والثاني لأن المظلل المكتوب بلغة HLSL والمرفق مع Cal3D لا يعمل بشكل صحيح! ولكن المظلل الآخر المكتوب بلغة التجميع Assembly يعمل بدون أي مشاكل وهو سريع وصغير، ولكنه غير مناسب طبعاً هنا لأن التطبيق من المفترض أن يكون واضح ومباشر قدر الإمكان.

من خلال كتابتي لعدة مظللات على طول هذه المقالة، واجهتني عدة مشاكل وتوصلت إلى معلومات عن المظللات لم أكن أعرفها يوماً، سأخرج عن الموضوع قليلاً ولكن أعدك بأنها معلومات ستفيدك على المدى البعيد، كل ما أود ذكره نقطتين: •برامج المظللات المكتوبة باستخدام HLSL تعامل المصفوفات التي تُمرّر لها بترتيب الأعمدة column major من ما يعني إنها تتعارض مع طريقة تعامل C++ وهي بترتيب الصفوف row major، Direct3D يقوم بعملية قلب المصفوفات (transpose) بنفسه عندما تمررها إلى ثابت داخل مظلل ما باستخدام SetMatrix، ولكن مع ذلك من المفيد معرفة هذه المعلومة عندما تحاول القيام ببعض العمليات على المصفوفات داخل المظلل نفسه. (وبالمناسبة، برامج Cg رغم أنها مشابهه تماماً لـ HLSL، تستخدم row major، لا تسألني لماذا)

عندما تقوم بعملية ضرب مصفوفتين مختلفتي الأبعاد داخل مظلل باستخدام التعليمة mul، من الأفضل أن تتذكر قواعد ضرب المصفوفات مع الانتباه إلى مسألة column major أو row major، لأنك عندما تقوم بالعملية بالعكس لن يظهر أي خطأ ولكن ستظهر نتائج عجيبة تدفعك إلى ضرب رأسك بلوحة المفاتيح على أمل أن يحلّ ذلك المشكلة (أتكلم عن خبرة هنا... إلا إنّي قد ضربت لوحة المفاتيح برأسي وليس العكس :).

وطبعاً الضحية الأساسية التي عانت الأمرّين حتى فهم حضرتي هاتان النقطتان هو فنسنت لا غير، وكمثال عن ما وصلت إليه "الدناءة العلمية" من جهتي، يمكنك التأمل لفترة ما في الشكل التالي والذي يمثل إحدى التجارب الفاشلة طبعاً:

كيف تقوم بتصدير الشخصيات من برنامج التصميم؟

من أهم الخطوات ضمن عملية التحريك بأكملها تصدير الشخصية من برنامج التصميم ثلاثي الأبعاد، إن لم يتم عمل ذلك بطريقة صحيحة ستظهر مشاكل في عرض وتحريك الشخصية لا يمكن حلها إلا بإعادة تصدير الشخصية بشكل صحيح.
سوف أستعرض هنا أحد المصدّرات التي استخدمتها بنفسي لتصدير فنسنت من برنامج 3D Studio MAX الإصدارة 7 وقد واجهت بعض المشاكل الصغيرة في البداية، لأني لم أقم بالعملية بطريقة صحيحة، سوف أذكر الملاحظات المهمة والشروط التي يجب إتباعها للتصدير إلى Cal3D بشكل صحيح.

المُصَدّر نفسه في حقيقة الأمر يتألف من 4 مصدّرات تجدها جميعا من قائمة هيئات التصدير "Save as type:" عندما تختار "Export" من قائمة "File":

  • (Cal3D Skeleton File (*.CSF, *.XSF: لتصدير الهيكل العظمي للشخصية.
  • (Cal3D Mesh File (*.CMF, *.XMF: لتصدير المجسّمات المستخدمة للشخصية.
  • (Cal3D Material File (*.CRF, *.XRF: لتصدير الخامات المستخدمة في الشخصية.
  • (Cal3D Animation File (*.CAF, *.XAF: لتصدير السلاسل الحركية.

ملاحظة مهمة/ عندما تود تصدير شخصية باستخدام هذا المصدّر يجب أن تقوم بتصدير الهيكل العظمي أولاً، لأن بقية الأجزاء تعتمد عليه، في الحقيقة، من الأفضل اعتماد تسلسل التصدير التالي: الهيكل العظمي، الخامات، المجسّمات، الحركة.

تثبيت مصدّر Cal3D داخل 3D Studio MAX

اتبع الخطوات التالية:

  • انسخ ملف المكتبة الديناميكية cal3d.dll إلى مسار برنامج 3D Studio MAX لديك.
  • حسب إصدار 3D Studio MAX لديك، انسخ ملف cal3d_max_exporter.dle المناسب إلى مجلّد plugins التابع لـ 3D Studio MAX.
  • (اختياري) للحصول على واجهة Cal3D الجديدة التي تستند على MaxScript، اسحب ملف cal3d.mzp وأسقطه في نافذة 3D Studio MAX، ستجد أداة جديدة بالاسم "Cal3D Export" في تصنيف "File"، يمكنك سحب الأداة من صندوق حوار "Customize User Interface" من قائمة "Customize" وإضافة الأداة إلى أي شريط أدوات تختاره.

تصدير الهيكل العظمي من 3D Studio MAX

كما ذكرت، فإن أول جزء يجب عليك تصديره هو الهيكل العظمي، ومن الأفضل لك استخدام ميزة Biped وPhysique لعملية إنشاء الهيكل العظمي وتحديد أوزان الرؤوس لأن تلك هي الميزة المدعومة رسمياً من قبل المصدّر.

والآن، يجب عليك أن تختار الهيكل العظمي (وفقط الهيكل العظمي لا غير) يجب كذلك أن تكون الشخصية في وضع Figure-mode، إذا لم تكن كذلك اذهب إلى القائمة Motion على اليمين (تلك التي تبدو كعجلة متحركة) ومنها في قسم Biped يجب أن يكون الزر الأول (شكل شخصية) مضغوطاً، أنظر الشكل التالي للتوضيح:

ستلاحظ إن هذا الوضع يجعل الشخصية في وضعها الأساسي! وذلك يوضّح لماذا نحتاج اختياره قبل تصدير كل من الهيكل العظمي والمجسّمات.
والآن، اذهب إلى File->Export... و اختر Cal3D Skeleton exporter كهيئة ملف، ثم حدد المسار واسم الملف المطلوب، عندما تضغط Save سوف تظهر لك النافذة التالية:

يمكنك كما ترى تحديد أي عدد من العظام، ولكن من الأفضل ترك كل شيء كما هو، اضغط Finish الآن للتصدير.

تصدير الخامات من 3D Studio MAX

لكي تستطيع تصدير الخامات التي تستخدمها شخصيتك بطريقة صحيحة، هنالك خطوة عليك عملها أولاً، يجب عليك تغيير أسماء جميع الخامات المستخدمة في الشخصية بحيث تضيف رمزاً بهذا الشكل "[i]" لكل منها (وكأنها مصفوفة)، لكل خامة فهرس مختلف، على سبيل المثال، لو كانت شخصيتك تمتلك الخامات التالية أسماؤها: face, skin, cloth, sword يجب أن تعيد تسميتها عن طريق نافذة Material Editor إلى الأسماء التالية:

  • [face[0
  • [skin[1
  • [cloth[2
  • [sword[3

والآن أنت جاهز لتصدير الخامات، اختر مجسّمات الشخصية ثم اذهب إلى File->Export، حدّد Cal3D Material File كهيئة تصدير ثم حدّد مسار واسم ملف الخامة الأولى واضغط Save لتظهر لك النافذة التالية:

من القائمة الظاهرة ، يجب أن تختار خامة واحدة فقط للتصدير، ذلك يعني إنك يجب أن تصدّر كل خامة تستخدمها شخصيتك على حدة، أي تكرار عملية التصدير، إضافة الأقواس المربعة للأسماء تساعد كثيراً كما تلاحظ حيث يمكنك البدء بالصفر لكي لا تنسى تصدير أي خامة. بعد اختيار الخامة اضغط Next.

ستظهر لك نافذة تظهر ملفات الإكساء المعرفة من ضمن هذه الخامة ورقم الطبقة لكل ملف، يمكنك طبعاً تصدير أي عدد من الطبقات كل منها تملك إكساءاً مختلف، إن لم تكن الخامة المحدّد تمتلك إكساءاً، لن يظهر أي ملف هنا بالطبع.

يمكنك من هذه النافذة النقر المزدوج على أي اسم إكساء لتستطيع إعادة تسميته بأي أسم ترغب فيه. اضغط Finish لتصدير الخامة الحالية.
كرّر العملية لكل الخامات الأخرى ولا تنسى تسمية ملفات التصدير بأسماء تدل على رقم أو أسم الخامة.

تصدير مجسّمات الشخصية من 3D Studio MAX

يجب استخدام الوضع Figure-mode هنا أيضاً، لكّي تصدّر المجسّمات بصورة صحيحة يجب أن تختار جميع أجزاء المجسّم الواحدة تلو الأخرى وتصدر كل منها إلى ملف منفصل (لاحظ أنك لا تحتاج لتصدير العظام كمجسّم)، إذا كانت شخصيتك تتكون من مجسّم واحد فلا مشكلة، كل ما تحتاجه هو تصدير ذلك المجسّم. والآن اختر المجسّم الأول واذهب إلى File->Export، حدّد الهيئة Cal3D Mesh File وحدد مسار واسم الملف المطلوب، ثم اضغط Save لتظهر لك النافذة التالية:

كما تلاحظ فإن المصدّر يحتاج ملف الهيكل العظمي الذي قمت بتصديره في الخطوة السابقة، قم بتحديد ذلك الملف هنا إن لم يكن محدّداً بشكل صحيح ثم اضغط Next.

يطلب منك المصدّر هنا تحديد أقصى عدد من العظام التي يمكن أن تؤثر على رأس واحد، إن كنت تذكر فقد ذكرت ضمن مفهوم الجلد إن هذا العدد من الأفضل أن لا يتجاوز 4، قم بتغييره للرقم 4 أو أقل (حسب ما ترى).

أما القيمة الثانية فهي تحدد أقل وزن ممكن للرأس لكي يتم احتسابه، هذه القيمة جيدة كما هي. اضغط Next.

سوف يتم تصدير المجسّم المحدد، ولكن بعد ذلك ستظهر لك النافذة من جديد وهي تنتظر منك تحديد الخيار: "Autotmatically create progressive meshes" هذه الميزة تسّرع عملية تحديد مستوى دقة الشخصية في تطبيقك أو لعبتك، إن كنت ستستخدم ميزة درجة الدقة Level-of-Detail قم بتأشيرها. اضغط Next.

ستظهر لك نافذة أخرى تنتظر تحديد هذا الخيار: "Automatically create a spring system for unattached vertices"

كما تعلم فإن مكتبة Cal3D تدعم القدرة على إنشاء الملابس عن طريق نظام النوابض المدمج، إن قمت بتحديد هذا الخيار سوف يتم اعتبار أي رؤوس غير مرتبطة بأي عظم جزءاً من الملابس، تذكّر إن هذا الخيار تجريبي لذلك فقد تكون هنالك مشاكل معينة بخصوصه. اضغط Finish.

كرر هذه العملية لجميع المجسّمات التي تتكون منها شخصيتك.

تصدير السلاسل الحركية من 3D Studio MAX

أول شيء عليك عمله، الخروج من وضع Figure-mode، أولاً اختر أي عظمة ثم اضغط الزر الخاص بـ Figure-mode مرة أخرى لتعطيله (أنظر الشكل 1). عند تصدير الحركة يجب أن تكون الشخصية خارج وضع Figure-mode.

والآن، اختر مجسّمات الشخصية ثم اذهب إلى File->Export وحدّد Cal3D Animation File كهيئة تصدير، ثم حدد مساراً وسمّي الملف اسماً مميزاً للحركة التي أنت بصدد تصديرها (على سبيل المثال: warrior_animation).

اضغط Save، لتظهر لك النافذة التي رأيتها مسبقاً والتي تطلب تحديد الهيكل العظمي للشخصية، حدّد ذلك إذا لم يكن محدّداً بشكل صحيح.

اضغط Next لتظهر لك النافذة التالية:

يمكنك كما تلاحظ أن تختار عظاماً محدّدة، تمكنك هذه الميزة من تصدير حركة تقتصر فقط على العظام التي اخترتها، مثلاً لو أردنا تصدير حركة التلويح باليد، نستطيع تحديد العظام التي تشكّل اليد فقط، حينها يمكن عن طريق التطبيق مزج حركة المشي مثلاً مع حركة تلويح اليد بشكل صحيح.

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

في هذه النافذة، يجب أن تقوم بتحديد معلومات الحركة المطلوب تصديرها، لنفترض أن شخصيتك تقوم بحركتين على طول خط الحركة الزمني، الأولى هي حركة مشي تبدأ من 1 وتنتهي عند 30 والثانية هي حركة ركض تبدأ من 31 وتنتهي بـ 71، ولنفترض أنك تستخدم معدّل عرض الأطر القياسي (30 إطاراً في الثانية). بالنسبة لحركة المشي ستكون القيم كالتالي:

Start Frame = 1
 End Frame = 30

واترك الباقي على حاله.

أما عند تصدير حركة الركض ستكون القيم كالتالي: Start Frame = 31
 End Frame = 71

واترك الباقي على حاله

اضغط Finish لتصدير ملف الحركة. كرّر العملية لجميع الحركات التي تقوم بها شخصيتك.

إعداد الشخصية لتعمل مع cal3d_miniviewer

يأتي Cal3D مجهزاً بمجموعة تطبيقات تستطيع عرض الشخصيات تبدأ بالاسم cal3d_miniviewer، تختلف فيما بينها في استخدام الواجهة الرسومية، حيث إن ثلاثة يستخدمون Direct3D واثنان يستخدمان OpenGL:

  • miniviewer_d3d: يستخدم ممر الرسوميات الثابت للإظهار
  • miniviewer_d3d_vs: يستخدم ممر الرسوميات القابل للبرمجة للإظهار (Direct3D)
  • miniviewer_d3d_bump: يستخدم ممر الرسوميات القابل للبرمجة للإظهار أيضاً (Direct3D) ويقدم دعماً لعرض خرائط النتوءات (فعلياً دعم إكساءات النواظم).
  • miniviewer_gl: يستخدم ممر الرسوميات الثابت للإظهار (OpenGL)
  • miniviewer_gl_vp: يستخدم ممر الرسوميات القابل للبرمجة للإظهار (OpenGL) لكي تعرض شخصيتك باستخدام هذه التطبيقات البسيطة، يجب أن تنشئ يدوياً ملف بامتداد cfg يحتوي على جميع المعلومات المطلوبة للشخصية، يمكنك كتابة الملف بأي برنامج إنشاء نصوص (Notepad مثلاً) ليكون بالشكل التالي:
# هذا تعليق سيتم تجاهله
#
# My Character
#
scale=0.5
# Skeleton
skeleton=character_skeleton.csf
# Meshes
mesh=character_mesh1.cmf
# كرّر لكل مجسّم في الشخصية
# Animations
animation=character_walk.caf
animation=character_run.caf
# كرّر لكل حركة تمتلكها الشخصية
# Materials
material=material_0.crf
# كرّر لكل خامة تمتلكها الشخصية

والآن، خزن هذا الملف بأي اسم بامتداد cfg في نفس المسار الذي صدّرت له الشخصية.

كل ما عليك القيام به الآن هو سحب ملف cfg وإسقاطه على أيقونة تطبيق العرض (cal3d_miniviewer) وسوف يقوم التطبيق بعرض شخصيتك بكل كبرياءها!

رخصة استخدام مكتبة Cal3D

من المهم فهم ضوابط الرخصة التي تعتمدها أي مكتبة تود أن تستخدمها، تعتمد Cal3D على رخصة تسمّى LGPL (Lesser GPL) من الإصدارة 2.1 تنتمي هذه الرخصة إلى مجموعة رخص البرمجيات الحرّة والتي تعني أن المكتبة سيتم تقديمها بكامل الشفرة المصدرية لها مع إعطاء الحرية للمستخدم بتعديل المكتبة كيفما شاء واستخدامها في أي تطبيق غير ربحي كيفما شاء.

وتسمح لنا هذه المكتبة أيضاً بالاستخدام في أي تطبيق أو لعبة تجارية، ولكن هنالك ضوابط يجب علينا الامتثال لها (لن تدفع دولاراً واحد.. أعدك).

لتبسيط الأمور، لتخلّص نفسك من الضوابط استخدم المكتبة كما هي (من دون تعديل) عن طريق الربط الديناميكي (Dynamic Linking أي عندما تستخدم DLL المكتبة – جميع الأمثلة هنا تستخدم المكتبة هكذا) بدل الربط الإستاتيكي (Static Linking)، ما دمت ستلتزم بهاتين النقطتين يمكنك استخدام المكتبة في أي تطبيق أو لعبة تجارية مجاناً بالكامل ومن دون حتى أن تذكر إنك استخدمتها!

أما إن أردنا تعديل المكتبة بأي شكل واستخدامها في تطبيق تجاري يجب علينا أن نعيد نشر الشفرة المصدرية الجديدة للمكتبة فقط بأنفسنا تحت الرخصة نفسها (LGPL) عبر الإنترنت ليستطيع أي شخص تحميلها، أي إن هذه المكتبة تعتمد فلسفة "إن عدلتها واستخدمتها تجارياً يجب عليك أن تشاركها مع المجتمع الذي طوّرها بالأساس "، و أنا شخصياً أرى ذلك شيئ منطقي جداً.

أما إن أردنا استخدام المكتبة عن طريق الربط الإستاتيكي، أي حينما نضمّن الشفرة المصدرية للمكتبة مع التطبيق، فيجب حينئذ نشر كود التطبيق بكامله بنفس الرخصة (LGPL) لأن التطبيق الناتج يعتبر نسخة معدّلة من المكتبة، ببساطة تجنب الربط الإستاتيكي وستكون بأمان.

التخرّج!

وأخيراً وصلنا لنهاية الرحلة، أرجو أن تكون استمتعت واستفدت.

مكتبة Cal3D هي مكتبة رائعة، ورغم أنها تمتلك بعض العيوب إلا أن أساسها متين، هنالك بالتأكيد مكتبات أفضل وأقوى، ولكن Cal3D تمتلك ميزتين مهمتين تجعلها فريدة من نوعها، أولاً إنها مفتوحة المصدر، إن لم يعجبك ما هو موجود الآن في المكتبة أو تود مزايا أكثر، يمكنك أن تقوم بكتابتها بنفسك! وبالطريقة التي ترغب فيها، والميزة الثانية هي أنها مستقلة تماماً عن أي نظام تشغيل أو محرّك أو واجهة رسومية ، يمكنك أن تستخدمها أينما شئت وكيفما شئت.

لقد وعدتك في البداية أنك سترى ما تعلمنا (أو على الأقل جزء مما تعلمنا) مطبقاً في مشروع كامل، وها أنا أقدم لك تطبيق التخرّج ونهاية المقالة وهو لعبة صغيرة تستخدم مكتبة Cal3D للحركة وAudiere للصوتيات، ومن البطل يا ترى؟ فنسنت بالتأكيد!

تطبيق التخرّج – لعبة الذاكرة

لقد أزعجنا فنسنت بمزاجه العكر وحان وقت الانتقام! لنرى كم ذاكرته جيدة... في لمح البصر، وجد فنسنت نفسه يقف في ساحة، يقطع طريقه حائط يحوي 4 رموز غريبة الشكل، تبدو وكأنها خرجت من كتاب خيالي أو شيء من هذا القبيل، أمام فنسنت هنالك 6 منصات تشبه الأزرار الكبيرة، و فنسنت الآن حائر أشد حيرة، كيف سيخرج وماذا سيفعل؟

أزرار التحكم:

  • W/S/A/D: للمشي
  • Shift + W: للركض
  • Space: التفاعل
  • Escape: الخروج من اللعبة

تلميح: يستطيع فنسنت أن يتفاعل مع شيء قريب (تحته مثلاً؟) عن طريق ضغط Space.

المعلومات التقنية:

  • تم استخدام مكتبة Cal3D لتحميل وتحريك فنسنت بواسطة مظلّل الرؤوس الذي تم استخدامه أيضاً في المثال السابق (تطبيق تحميل وتحريك فنسنت PP).
  • لاحظ التحويلات الناعمة بين السلاسل الحركية المختلفة، لقد تم تحقيق ذلك (كما حزرت بالتأكيد) بواسطة مزج الحركة.
  • استخدام مكتبة Audiere للصوتيات، وهي مكتبة جيدة جداً مفتوحة المصدر من نفس رخصة Cal3D، أي LGPL.

ألعاب وتطبيقات تستخدم Cal3D

تم استخدام Cal3D في عدد من التطبيقات والألعاب التي منها تجاري، هذه بضعة أمثلة:

لمزيد من المعلومات

أضف تعليقاً

Loading