هناك عدد أقل من الأشخاص الذين يمكن أن يفاجأوا بالواقع المعزز (AR). بالنسبة للبعض ، ترتبط هذه التكنولوجيا بلعبة لبضع ساعات. يجدها آخرون أكثر عملية.
اسمي ديمتري وأعمل على تطوير Yandex.Maps لنظام iOS. اليوم سأخبر قراء هبر كيف ابتكرنا التوجيه باستخدام الواقع المعزز. سوف تتعلم أيضًا عن ميزات استخدام إطار عمل ARKit ، والذي بفضله لم يعد إدخال الواقع المعزز موضع اهتمام المتخصصين فقط في مجال رؤية الكمبيوتر.

في عام 2009 ، كانت مجلة Esquire هي الأولى بين وسائل الإعلام التي أضافت دعم الواقع المعزز إلى منتجها. على غلاف المجلة نشر رمز يمكنك أن ترى روبرت داوني جونيور "العيش".

لم يقتصر استخدام الواقع المعزز في صناعة الترفيه على ذلك. من الأمثلة الحية كانت لعبة Pokemon Go ، التي تم إصدارها في عام 2016. بحلول يوليو من ذلك العام ، تم تنزيله أكثر من 16 مليون مرة. أدى نجاح اللعبة إلى ظهور العديد من النسخ المستنسخة مع AR.
يمكن اعتبار الأحداث المهمة في صناعة الواقع المعزز في السنوات الأخيرة إعلانات Google Glass و Microsoft Hololens. يظهر مظهر هذه الأجهزة الناقل الذي تتحرك فيه الشركات الكبيرة.
آبل ليست استثناء. في عام 2017 ، قدمت الشركة إطار ARKit ، الذي لا يمكن المبالغة في أهميته بالنسبة للصناعة. وسنتحدث عنه بمزيد من التفصيل.
ARKit
ميزات ARKit ، مما يسهل استخدام الواقع المعزز:
- عدم الحاجة لعلامات (علامات) خاصة ،
- التكامل مع أطر رسومات Apple 2D / 3D الموجودة - SceneKit و SpriteKit و Metal و
- دقة عالية في تحديد موضع واتجاه الجهاز في الفضاء ،
- لا حاجة لمعايرة الكاميرا أو أجهزة الاستشعار.
تحت غطاء ARKit هو نظام قياس المسافات بالقصور الذاتي البصري الذي يجمع بين البيانات مع الأنظمة الفرعية البصرية (الكاميرا) والقصور (مقياس التسارع ، الجيروسكوب) للجهاز لتحديد الموقع والإزاحة على المسرح. عنصر الاتصال في هذا النظام هو مرشح كالمان - خوارزمية تقوم في كل لحظة من الوقت بتحديد أفضل قراءات النظامين الفرعيين وتزويدنا بها في شكل موقفنا وتوجهنا على المسرح. لدى ARKit أيضًا "فهم" للمشهد - يمكننا تحديد الأسطح الأفقية والرأسية ، بالإضافة إلى ظروف الإضاءة للمشهد. وبالتالي ، عند إضافة كائن إلى المشهد ، يمكننا إضافة إضاءة افتراضية إليه ، وبفضله سيبدو الكائن أكثر واقعية.
بالمناسبةقريبًا ، سيتم إصدار الإصدار الإطاري 2.0 ، والذي ستتم فيه إضافة ميزات جديدة وتحسين دقة تحديد المواقع بشكل كبير.
سمحت ARKit للمطورين بتضمين الواقع المعزز عالي الجودة في تطبيقاتهم مع بذل جهد أقل بكثير. سنوضح ذلك باستخدام مثال Yandex.Maps.
التوجيه باستخدام الواقع المعزز على Yandex.Maps
عادة ، بعد الإعلان عن الإصدار الجديد من iOS ، تجتمع العديد من الفرق في Yandex لمناقشة إمكانية إدخال ميزات جديدة في تطبيقاتهم. فعل فريق Yandex.Mart نفس الشيء. في غضون شهر من لحظة الإعلان عن ARKit ، غالبًا ما ناقشنا كيفية تنفيذه في الخرائط. ما نوع الأفكار التي لم نسمعها من بعضنا البعض! بسرعة ، توصلنا إلى استنتاج مفاده أن أحد الحلول الأكثر فائدة والسطحية هو استخدام الواقع المعزز في التوجيه.
يرجع اختيار هذه الفكرة إلى حقيقة أن العديد من مستخدمي البطاقات غالبًا ما يواجهون موقفًا عندما تجد نفسك في منطقة غير مألوفة وتحتاج إلى أن تقرر بسرعة إلى أين تذهب. تتمثل الطريقة القياسية لمستخدم الخريطة العادي في فتح التطبيق ، وبناء طريق للمشاة ، وتحديد المكان الذي يجب الانتقال إليه ، والتشغيل في مكانه. تتمثل فكرة إدخال الواقع المعزز في توجيه المشاة في إنقاذ المستخدم من الإجراءات غير الضرورية ، مما يعرض على الفور المكان الذي تحتاج فيه إلى الانتقال مباشرة فوق صورة الكاميرا.
أولاً ، أريد أن أقول بضع كلمات حول التوجيه. ماذا أضع في هذا المفهوم؟ من وجهة نظر التنفيذ في تطبيق الهاتف المحمول ، هذه مجموعة قياسية إلى حد ما من الخطوات التي تسمح للمستخدم بالانتقال من النقطة A إلى النقطة B:
- اختيار نقاط المغادرة والوصول ،
- تلقي طريق في شكل مجموعة من النقاط في الإحداثيات الجغرافية (خطوط العرض ، خطوط الطول) ،
- عرض على خريطة خط الطريق ،
- مرافقة المستخدم بمعلومات إضافية أثناء التحرك على طول الطريق.
لن نتطرق إلى النقطتين الأوليين. لا يسعني إلا أن أقول أننا نحصل على الطريق من خلال مكتبة Yandex.Mapkit عبر الأنظمة الأساسية ، والتي تتوفر لك أيضًا في شكل جراب. كيف يختلف توجيه الواقع المعزز عن التوجيه القياسي في الخرائط؟ بادئ ذي بدء ، الفرق الرئيسي هو خريطة مخفية بالكامل تقريبًا. يتم التركيز بشكل رئيسي على منطقة الشاشة مع صورة دفق الفيديو من الكاميرا ، حيث يتم فرض عناصر مرئية إضافية (علامة الإنهاء ، والعلامة المساعدة وصورة خط المسار). كل عنصر من هذه العناصر المرئية له حمله الدلالي ومنطقه الخاص (متى وكيف يجب عرضه). سننظر في دور كل عنصر من هذه العناصر بمزيد من التفصيل في وقت لاحق ، ولكن الآن أقترح النظر في المهام التي كانت أمامنا:
- تعلم وضع الأشياء على مشهد ARKit ، ومعرفة إحداثياتها الجغرافية ،
- تعرف على كيفية رسم واجهة المستخدم الضرورية على مشهد ثلاثي الأبعاد بأداء كافٍ.
نحن بحاجة إلى تحويل إحداثيات النقاط من الإحداثيات الجغرافية إلى الإحداثيات على المسرح ، وتحديد النقاط التي سيتم عرضها ، وعرض كل واجهة المستخدم الضرورية أعلى صورة الكاميرا في الموضع الصحيح. لكن كل شيء تبين أنه أكثر تعقيدًا قليلاً مما بدا للوهلة الأولى.
قبل البدء في تنفيذ الميزات مباشرةً ، تم تكليف أحد زملائي بمهمة إعداد نموذج أولي يوضح إمكانية (أو استحالة) تنفيذ وظائف مماثلة باستخدام مجموعة من الأدوات التي يمكن الوصول إليها. لمدة أسبوعين ، شاهدنا San Sanych وهو يحرث المساحات المفتوحة للمساحات المفتوحة والمناطق المحيطة بمكتبنا بهاتف في متناول اليد وننظر إلى العالم من حولنا من خلال منظور الكاميرا. ونتيجة لذلك ، حصلنا على نموذج أولي يعمل يوضح كل نقطة من المسار كعلامة على المسرح مع مسافة إليها. بمساعدة هذا النموذج الأولي ، كان من الممكن ، مع مجموعة ناجحة من الظروف ، الانتقال من العمل إلى المترو ولا حتى الضياع. لكنه أكد بجدية إمكانية تنفيذ الوظيفة المقصودة. ولكن لا يزال هناك عدد من المهام التي لا يزال يتعين على فريقنا حلها.
بدأ كل شيء بدراسة الأدوات. في ذلك الوقت ، كان هناك شخص واحد فقط في الفريق لديه خبرة في العمل مع الرسومات ثلاثية الأبعاد. دعونا نلقي نظرة سريعة على الأدوات التي سيتعين على أي شخص يفكر في تنفيذ مثل هذه الأفكار مع ARKit التعامل معها.
الأدوات وواجهات برمجة التطبيقات
المهمة الرئيسية لتقديم الكائنات هي إنشاء وإدارة كائنات مشهد إطار عمل SceneKit. مع ظهور ARKit ، أصبحت فئة ARSCNView (سليل فئة SCNView - الفئة الأساسية للعمل مع المشهد في SceneKit) متاحة للمطور ، الذي يحل معظم المهام التي تستغرق وقتًا طويلاً لدمج ARKit و SceneKit ، وهي:
- تزامن موقع الهاتف في الفضاء مع وضع الكاميرا على المسرح ،
- يتزامن نظام الإحداثيات للمشهد مع نظام الإحداثيات ARKit ،
- كخلفية للمشهد ، يتم استخدام دفق الفيديو من كاميرا الجهاز.
يوفر كائن ARSCNView للمطور أيضًا كائن جلسة الواقع المعزز الذي يمكن بدءه بالتكوين اللازم ، أو إيقافه ، أو الاشتراك في أحداث مختلفة باستخدام كائن المفوض.
لإضافة كائنات إلى المشهد ، يتم استخدام الورثة أو كائنات SCNNode مباشرة. تمثل هذه الفئة موضعًا (ناقل ثلاثي الأبعاد) في نظام الإحداثيات الخاص بوالده. وبالتالي ، نحصل على شجرة من الكائنات على المشهد مع جذر في كائن خاص - الجذر عقدة المشهد. كل شيء هنا يشبه إلى حد كبير التسلسل الهرمي لكائنات UIView في UIKit. يمكن عرض كائنات SCNNode على المسرح عند إضافة المواد والإضاءة.
لإضافة الواقع المعزز إلى تطبيق الهاتف المحمول ، تحتاج أيضًا إلى معرفة الأشياء الرئيسية لـ ARKit API. الرئيسي هو موضوع جلسة الواقع المعزز - ARSession. يقوم هذا الكائن بمعالجة البيانات وهو مسؤول عن دورة حياة جلسة الواقع المعزز. الغرض من هذه المقالة ليس إعادة بيع وثائق ARKit و SceneKit ، لذلك لن أكتب عن جميع معلمات التكوين المتاحة لجلسة الواقع المعزز ، ولكن سأركز على واحدة من أهم المعلمات لتكوين جلسة الواقع المعزز لتطبيقات التنقل - worldAlignment. تحدد هذه المعلمة اتجاه محاور المشهد في وقت تهيئة الجلسة. بشكل عام ، عند تهيئة جلسة الواقع المعزز ، يقوم ARKit بإنشاء نظام إحداثيات مع بداية عند نقطة تتزامن مع الموقع الحالي للهاتف في الفضاء ، ويوجه محور هذا النظام اعتمادًا على قيمة خاصية woldAlignment. في تطبيقنا ، يتم استخدام قيمة الجاذبية والرأس ، مما يعني أن المحاور سيتم توجيهها على النحو التالي: المحور Y - في الاتجاه المعاكس للجاذبية ، المحور Z - إلى الجنوب ، والمحور X - إلى الشرق.

مع مجموعة جيدة من الظروف ، سيتم محاذاة محاور X / Z بالفعل مع التوجيهات إلى الجنوب / الشرق ، ولكن ، بسبب الأخطاء في قراءات البوصلة ، يمكن توجيه المحاور بزاوية معينة إلى الاتجاه الموصوف في الوثائق. هذه واحدة من المشاكل التي كان علينا أن نتعامل معها ، ولكن المزيد حول ذلك لاحقًا.
الآن بعد أن فحصنا الأدوات الأساسية ، دعونا نلخص: تعيين مسار باستخدام SceneKit يضيف كائنات SCNNode إلى المشهد في المواضع التي تم الحصول عليها عن طريق التحويل من الإحداثيات الجغرافية إلى إحداثيات المشهد. قبل أن نتحدث عن التحويل المنسق وعمومًا عن وضع الكائنات على المشهد ، دعنا نتحدث عن مشاكل عرض عناصر واجهة المستخدم ، بافتراض أننا نعرف موقف الكائنات على المسرح.
علامة الإنهاء
العنصر المرئي الرئيسي لتوجيه المشاة مع الواقع المعزز هو علامة النهاية ، التي تعرض نقطة النهاية للمسار. أيضًا أعلى العلامة ، نعرض للمستخدم المسافة إلى نقطة نهاية المسار.

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

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

في وقت التنفيذ ، لم نتمكن من إيجاد طريقة لتحديد حجم الكائن في المشهد ، وقررنا رسم علامة النهاية باستخدام UIKit. في هذه الحالة ، تكرر الخوارزمية الخطوات من 1 إلى 5 ، وبعد ذلك يتم رسم دائرة بالحجم المطلوب على الشاشة مع المركز عند النقطة التي تم الحصول عليها في الخطوة 2 باستخدام أدوات UIKit. يمكن العثور على مثال على تنفيذ تسمية باستخدام UIKit هنا .
بضع كلمات حول الرمزفي نهاية المقال قدمت عدة روابط لمواد مفيدة ومثيرة للاهتمام ، بما في ذلك العينات ، حيث يمكنك إلقاء نظرة تفصيلية على الكود الحقيقي الذي يحل المشاكل المقدمة في المقالة وينفذ الخوارزميات المقدمة. الاهتمام الرئيسي في رأيي هو النموذج الأولي لتوجيه المشاة ، والذي يجمع بين جميع الوظائف ، باستثناء آلية تعديل المحور ، والتي يتم وصفها بالتفصيل أدناه.
الرمز أعلاه لا يدعي أنه مثالي واكتمال وجودة الإنتاج =)
يكمن الفرق بين استخدام SceneKit و UIKit في هذه الحالة أيضًا في حقيقة أنه عند التنفيذ على SceneKit ، سيتم إنشاء كائن SCNNode لنقطة نهاية المسار (علامة النهاية) باستخدام المواد والهندسة ، حيث يجب أن يكون مرئيًا ، أثناء استخدام UIKit نحتاج إلى كائن العقدة حصريًا للبحث عن الإسقاط على مستوى الشاشة (لتحديد مركز العلامة على الشاشة). في هذه الحالة ، لا يلزم إضافة الهندسة والمواد. لاحظ أنه يمكن العثور على المسافة من الكاميرا إلى كائن SCNNode لنقطة نهاية المسار بطريقتين - باستخدام الإحداثيات الجغرافية للنقاط ، أو كطول المتجه بين النقاط في المشهد. هذا ممكن لأن كائن الكاميرا هو خاصية SCNNode. للحصول على عقدة الكاميرا ، تحتاج إلى الرجوع إلى خاصية pointOfView لمشهدنا.
تعلمنا كيفية تحديد نصف قطر عقدة علامة النهاية عند نقطة عشوائية في الوقت المناسب عند التنفيذ على SceneKit وموقف عرض علامة النهاية إذا تم تنفيذها على UIKit. يبقى أن نفهم متى يكون من الضروري تحديث هذه القيم؟ هذا المكان هو أسلوب كائن SCNSceneRendererDelegate:
renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval)
يتم استدعاء هذه الطريقة بعد كل إطار مشهد تم تقديمه. من خلال تحديث قيم الخصائص في نص هذه الطريقة ، نحصل على ملصق إنهاء معروض بشكل صحيح.
الرسوم المتحركة
بعد ظهور علامة النهاية في dev ، تابعنا لإضافة الرسوم المتحركة المتتالية لهذه العلامة. أعتقد أنه بالنسبة لمعظم مطوري iOS إنشاء الرسوم المتحركة ليست مشكلة كبيرة. ولكن عند التفكير في طريقة التنفيذ ، واجهنا مشكلة التحديث المستمر لإطار رؤيتنا. لاحظ أنه في معظم الحالات ، تتم إضافة الرسوم المتحركة إلى كائنات UIView الثابتة. مشكلة مماثلة - يحدث تحديث مستمر لنصف قطر هندسة العقدة عند تنفيذه باستخدام SceneKit. والحقيقة هي أن الرسوم المتحركة النابضة ترجع إلى الرسوم المتحركة لحجم الدائرة (لـ UIKit) ونصف قطر الكرة (ل SceneKit). نعم ، نعم ، نحن نعلم أنه في UIKit يمكن القيام بهذا النوع من الرسوم المتحركة باستخدام CALayer ، ولكن من أجل بساطة سرد القصص ، قررت النظر في هذه المشكلة بشكل متناظر لإطارين العمل. ضع في الاعتبار تنفيذ على UIKit. إذا قمت بإضافة رمز يقوم بتحريك نفس الإطار إلى التعليمات البرمجية الموجودة التي تقوم بتحديث إطار العرض ، فسوف تتم مقاطعة الرسوم المتحركة عن طريق تعيين الإطار بشكل صريح. لذلك ، كحل لهذه المشكلة ، قررنا استخدام الرسوم المتحركة لخاصية convert.scale.xy لكائن UIView. عند تنفيذ استخدام SceneKit ، سيكون عليك إضافة حركة لخصائص المقياس إلى كائن SCNNode. الشيء الجميل في استخدام SceneKit في هذه الحالة هو حقيقة أنه يدعم CoreAnimation بالكامل ، لذا فإن تعلم واجهة برمجة تطبيقات جديدة ليس ضروريًا. الكود الذي يطبق الرسوم المتحركة على غرار الرسوم المتحركة في Yandex.Maps يبدو مثل هذا:
let animationGroup = CAAnimationGroup.init() animationGroup.duration = 1.0 animationGroup.repeatCount = .infinity let opacityAnimation = CABasicAnimation(keyPath: "opacity") opacityAnimation.fromValue = NSNumber(value: 1.0) opacityAnimation.toValue = NSNumber(value: 0.1) let scaleAnimation = CABasicAnimation(keyPath: "scale") scaleAnimation.fromValue = NSValue(scnVector3: SCNVector3(1.0, 1.0, 1.0)) scaleAnimation.toValue = NSValue(scnVector3: SCNVector3(1.2, 1.2, 1.2)) animationGroup.animations = [opacityAnimation, scaleAnimation] finishNode.addAnimation(animationGroup, forKey: "animations")
لوحة
في بداية المقال ، أشرت إلى لوحة إعلانية بمسافة تصل إلى نقطة نهاية المسار ، وهي في جوهرها تسمية تحتوي على نص يقع دائمًا فوق علامة النهاية. بالتقليد ، سأوجز المشاكل المتأصلة في عمليات التنفيذ على UIKit و SceneKit ، لأخبرنا عن الحلول الممكنة لكل من الأطر.
لنبدأ مع UIKit. في هذه الحالة ، تكون لوحة الإعلانات عبارة عن UILabel عادي ، حيث يتم تحديث النص باستمرار ، ويعرض المسافة إلى نقطة نهاية المسار. دعونا نلقي نظرة على المشكلة التي نواجهها.

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

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

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

يتم حل هذه المشكلة باستخدام واجهة برمجة تطبيقات SCNBillboardConstraint. بإضافة ثابت مع المحور الحر Y إلى مجموعة ركائز العقدة ، نحصل على عقدة تدور حول المحور Y لنظام الإحداثيات الخاص بها ، بحيث يتم توجيهها دائمًا نحو الكاميرا. المهمة الوحيدة للمطور هي وضع هذه العقدة في الارتفاع الصحيح بحيث تكون لوحة الإعلانات ذات المسافة مرئية للمستخدم دائمًا.
let billboardConstraint = SCNBillboardConstraint() billboardConstraint.freeAxes = SCNBillboardAxis.Y finishNode.constraints = [billboardConstraint]
علامة المساعد
واحدة من الميزات الرئيسية لتوجيه المشاة مع الواقع المعزز داخل الفريق نعتبر علامة مساعدة - عنصر مرئي خاص يظهر على الشاشة في اللحظة التي تغادر فيها نقطة النهاية للمسار منطقة الرؤية وتوضح للمستخدم مكان تحويل الهاتف بحيث تظهر العلامة على الشاشة خط النهاية.

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

نقطة التقاطع التي تم العثور عليها هي المركز المطلوب للعلامة المساعدة. قياساً على الكود الذي يحدّث معلمات ملصق النهاية ، وضعنا الكود الذي يعرض الملصق الإضافي في طريقة التفويض التي تم التعبير عنها أعلاه.
خط متعدد الخطوط
بعد إنشاء مسار ورؤية علامة النهاية على الشاشة ، يمكن للمستخدم الوصول إليه عن طريق التوجيه فقط في اتجاه العلامة ، ولكن يتم استدعاء التوجيه لأنه يوضح المسار للمستخدم. اعتقدنا أنه سيكون من الغريب جدًا تقليل وظيفة توجيه المشاة ، باستثناء عرض المسار من إصدار AR. لتصور خط المسار ، تقرر عرض مجموعة من الأسهم تتحرك على طوله. في هذه الحالة ، كان المصممون راضين عن اختفاء الأسهم عمليًا عند الابتعاد (سيتم تحديد الحجم وفقًا لقواعد إسقاط المنظور) ، وتقرر استخدام SceneKit للتنفيذ.
قبل الشروع في وصف التنفيذ ، من المهم ملاحظة أنه حسب التصميم ، كان يجب أن تكون الأسهم على مسافة 3 أمتار من بعضها البعض. إذا قمت بتقدير عدد الأشياء (الأسهم) التي يجب تقديمها بمسار طوله حوالي 1 كم ، فسيكون حوالي 330 قطعة. في نفس الوقت ، تتم إضافة كل كائن متحرك إلى الحركة على طول الجزء الخاص به من المسار. لاحظ أن الأسهم البعيدة عن موضع الكاميرا على المسرح على مسافة حوالي 100-150 مترًا غير مرئية عمليًا نظرًا لصغر حجمها. بعد أخذ هذه العوامل في الاعتبار ، تقرر عدم عرض جميع الكائنات ، ولكن فقط عرض تلك التي تمت إزالتها من المستخدم لا يزيد عن 100 متر على طول خط المسار ، وتحديث مجموعة الكائنات المعروضة بشكل دوري. نحن نعرض كمية كافية من المعلومات المرئية ، ونزيل حسابات SceneKit غير الضرورية ونوفر بطارية المستخدم.

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

إنشاء نموذج ثلاثي الأبعاد
دعونا نفكر بمزيد من التفصيل في عملية إنشاء نموذج ثلاثي الأبعاد. في معظم الحالات ، كل ما عليك القيام به لإنشاء نموذج ثلاثي الأبعاد بسيط (مثل سهمنا) هو فتح أي محرر ثلاثي الأبعاد ، وقضاء بعض الوقت في إتقانه وجعل هذا النموذج فيه. إذا كان لدى اللاعبين من فريقك خبرة في النمذجة ثلاثية الأبعاد ، أو كان لديهم الوقت للتعلم ، على سبيل المثال ، 3DMax (ويجب شراؤها) ، فأنت محظوظ بشكل لا يصدق. لسوء الحظ ، في وقت تنفيذ هذه الميزة ، لم يكن لدى أي منا أي خبرة خاصة ، لم يكن هناك وقت فراغ للتدريب ، لذلك كان علينا أن نصنع نموذجًا ، إذا جاز التعبير ، بوسائل مرتجلة. أعني وصف النموذج في الكود. بدأ كل شيء بعرض نموذج ثلاثي الأبعاد على شكل مثلثات. ثم كان علينا العثور على إحداثيات رؤوس هذه المثلثات يدويًا في نظام إحداثيات النموذج ، ثم إنشاء مجموعة من مؤشرات رؤوس المثلثات. مع وجود هذه البيانات تحت تصرفنا ، يمكننا إنشاء الهندسة اللازمة مباشرة في SceneKit. يمكنك إنشاء نموذج مشابه لنموذجنا ، على سبيل المثال ، مثل:
class ARSCNArrowGeometry: SCNGeometry { convenience init(material: SCNMaterial) { let vertices: [SCNVector3] = [ SCNVector3Make(-0.02, 0.00, 0.00), // 0 SCNVector3Make(-0.02, 0.50, -0.33), // 1 SCNVector3Make(-0.10, 0.44, -0.50), // 2 SCNVector3Make(-0.22, 0.00, -0.39), // 3 SCNVector3Make(-0.10, -0.44, -0.50), // 4 SCNVector3Make(-0.02, -0.50, -0.33), // 5 SCNVector3Make( 0.02, 0.00, 0.00), // 6 SCNVector3Make( 0.02, 0.50, -0.33), // 7 SCNVector3Make( 0.10, 0.44, -0.50), // 8 SCNVector3Make( 0.22, 0.00, -0.39), // 9 SCNVector3Make( 0.10, -0.44, -0.50), // 10 SCNVector3Make( 0.02, -0.50, -0.33), // 11 ] let sources: [SCNGeometrySource] = [SCNGeometrySource(vertices: vertices)] let indices: [Int32] = [0,3,5, 3,4,5, 1,2,3, 0,1,3, 10,9,11, 6,11,9, 6,9,7, 9,8,7, 6,5,11, 6,0,5, 6,1,0, 6,7,1, 11,5,4, 11,4,10, 9,4,3, 9,10,4, 9,3,2, 9,2,8, 8,2,1, 8,1,7] let geometryElements = [SCNGeometryElement(indices: indices, primitiveType: .triangles)] self.init(sources: sources, elements: geometryElements) self.materials = [material] } } static func arrowBlue() -> SCNGeometry { let material = SCNMaterial() material.diffuse.contents = UIColor.blue material.lightingModel = .constant return ARSCNArrowGeometry(material: material) }
تبدو النتيجة النهائية كما يلي:

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

في بداية كل قسم ، نقوم بإنشاء كائن SCNNode للسهم ، الذي يتكون رسمه المتحرك في التحرك على طول قسمه.

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

يبدو لنا أن أسهل طريقة لتنفيذ مثل هذه الرسوم المتحركة باستخدام واجهة برمجة التطبيقات SCNAction - واجهة برمجة تطبيقات تعريفية تسمح لك بإنشاء رسوم متحركة متسلسلة وتجميعية ومتكررة بشكل ملائم. يمكنك إلقاء نظرة على التنفيذ بمزيد من التفاصيل هنا . نظرًا لحقيقة أن كل سهم ينهي حركته عند نقطة بداية قسم الرسوم المتحركة للسهم التالي ، يتم إنشاء الانطباع بالحركة المستمرة للسهم على طول القسم المحدد بالكامل من المسار.
في هذا الصدد ، أقترح استكمال النظر في الجوانب المختلفة للعرض والذهاب إلى الجزء الرئيسي - تحديد مواقف الأشياء على المسرح بواسطة الإحداثيات الجغرافية للأشياء.
تحديد موضع كائن في المشهد
نبدأ المحادثة حول تحديد موضع كائن ما في المشهد من خلال النظر في أنظمة الإحداثيات ، التي يجب إجراء التحويل بينها. يوجد 2 منهم فقط:
- إحداثيات جيوديسية (أو جغرافية للبساطة) - موضع الأشياء (نقاط الطريق) في العالم الحقيقي ،
- إحداثيات ديكارتية - موضع الأشياء على المشهد (في ARKit). تذكر أن نظام الإحداثيات للمشهد يتزامن مع نظام الإحداثيات ARKit (في حالة استخدام ARSCNView).
الترجمة من نظام إحداثي إلى آخر والعكس ممكن بسبب حقيقة أن الإحداثيات في ARKit تقاس بالأمتار ، ويمكن ترجمة الإزاحة بين إحداثيين جيوديسيين بدقة كبيرة إلى الإزاحة بالأمتار على طول محوري X و Z لنظام الإحداثيات ARKit عند الإزاحات الصغيرة. دعني أذكرك بأن الإحداثيات الجيوديسية هي نقاط ذات خط طول وخط عرض معين.
دعونا نتذكر هذه المفاهيم الهامة من مسار الجغرافيا مثل المتوازيات والخطوط الزوال ، وخصائصها الأساسية:
- الموازي هو خط بقيمة درجة خط العرض. أطوال المتوازيات المختلفة مختلفة.
- خط الطول - خط بقيمة درجة خط الطول. أطوال جميع خطوط الطول هي نفسها.
الآن دعونا نرى كيف يمكنك حساب الإزاحة بالأمتار ، بين إحداثيات جيوديسية بإحداثيات
و
:
، 
، 
شرحيزيح الإزاحة في الإحداثيات الجيوديسية خطيًا إلى أمتار فقط في حالات الإزاحة الصغيرة. في حالات النزوح الكبيرة ، من الضروري أن تأخذ بصدق جزء لا يتجزأ.
الآن بعد أن أصبحنا قادرين على ترجمة الإزاحة من نظام إحداثي إلى آخر ، نحتاج إلى اتخاذ قرار بشأن نقطة مرجعية - وهي النقطة التي يُعرف عنها التنسيق والإحداثيات الجغرافية في ARKit (الإحداثي على المسرح) في نفس الوقت. بعد العثور على هذه النقطة ، يمكننا تحديد إحداثيات أي كائن على المسرح ، ومعرفة إحداثياته الجغرافية واستخدام الصيغ أعلاه.
للتوضيح ، خذ بعين الاعتبار مثال:
في بداية جلسة الواقع المعزز ، طلبنا من CoreLocation تنسيقنا الجغرافي واستلمناها على الفور -
. إذ يشير إلى حقيقة أن أصل نظام إحداثيات ARKit في بداية الجلسة عند النقطة التي يقع فيها الجهاز ، حصلنا على النقطة المرجعية ، لأننا نعرف الإحداثيات الجغرافية والإحداثيات على المسرح
. دعونا نحتاج إلى إيجاد الإحداثيات على مشهد الكائن بإحداثية جغرافية
. للقيام بذلك ، ابحث عن الإزاحة بالأمتار بين الإحداثيات الجغرافية للكائن والإحداثيات الجغرافية للنقطة المرجعية ، ثم أضف الإزاحة التي تم العثور عليها إلى الإحداثيات في مشهد النقطة المرجعية. سيكون الإحداثيات الناتجة في المشهد هي التنسيق المطلوب.

ألاحظ أن الموضع على المشهد الموجود بهذه الطريقة سيتوافق مع موضع الكائن في العالم الحقيقي فقط إذا تم محاذاة المحور X / Z لنظام إحداثيات المشهد مع الاتجاهات إلى الجنوب / الشرق. محاذاة المحور ، من الناحية النظرية ، يجب أن تتحقق عن طريق تعيين علم المحاذاة على العالم على الجاذبية والرأس. ولكن كما قلت في بداية المنشور ، فإن هذا ليس هو الحال دائمًا.
دعونا نفكر بمزيد من التفصيل في طريقة تحديد النقطة المرجعية. للقيام بذلك ، نقدم مفهوم التقدير - مجموعة من الإحداثيات والإحداثيات الجغرافية على المسرح.

قد لا يتم دائمًا استخدام الطريقة المقترحة أعلاه لتحديد النقطة المرجعية. في وقت بدء جلسة الواقع المعزز ، قد لا يتم تنفيذ طلب CLLocation للمستخدم على الفور ، علاوة على ذلك ، قد تكون دقة الإحداثيات التي تم الحصول عليها خطأ كبير. سيكون من الأصح أن تطلب من SceneKit الحصول على منصب على المسرح في اللحظة التي نحصل فيها على القيمة من CoreLocation. في هذه الحالة ، يتم بالفعل الحصول على مكونات التقدير الناتج في نفس الوقت ، ولدينا الفرصة لاستخدام أي من التقديرات كنقطة مرجعية. عند العمل مع ARKit ، يتراكم خطأ الإزاحة بمرور الوقت ، لذلك لا توصي Apple باستخدام ARKit كأداة تنقل.
عندما قررنا تنفيذ توجيه المشاة باستخدام الواقع المعزز ، أجرينا القليل من البحث عن الحلول التي كانت موجودة في ذلك الوقت ، باستخدام ARKit لمهام مماثلة ، وتوصل إلى إطار ARKit + CoreLocation. كانت فكرة هذا الإطار أنه بفضل ARKit يمكننا تحديد موقع المستخدم بشكل أكثر دقة من استخدام CoreLocation حصريًا.
مفهوم ARKit + CoreLocation:
- عند تلقي CLLocation من CLLocationManager
- طلب موقف في المشهد باستخدام scene.pointOfView.worldPosition
- حفظ هذا الزوج الإحداثي (تقدير) إلى المخزن المؤقت
- الحصول على الموقع الدقيق إذا لزم الأمر
- اختر أفضل تقدير
- حساب الإزاحة بين الموضع الحالي على المسرح والموقف على مرحلة أفضل تقدير
, , CoreLocation, .
, « ». , .
(, ):
- ( horizontalAccuracy),
- ,
- 100 .
CoreLocation . , , CoreLocation , 100 .
, . , , ( 100 ).
, X/Z ARKit / . ARKit , , .
لماذا؟, (, IKEA, ), Y ARKit – , . gravity worldAlignment.
, . , , , . . AR . , , , , . AR.
, . ,
CLLocationManager
—
.
CLLocationManager —
وفقًا لذلك.
ARKit —
2 CoreLocation
.
. , CoreLocation . . ARKit /.

ARKit Y? . :
- ,
- ,
- ,
- ,
- .
. . CLLocationManager' , ( ), ( ).
1, 2 :
و
,
– 2, ARKit.
( Bearing).

. , ? , , , , . , , , horizontalAccuracy. , , , . :

, , .
. , . على سبيل المثال:
, , , , (), . , . , , . , , ( ). .
, . , , ( , , ).
الاختبار
, . , , , . 2 :
- , , , , .
. , , 100 CLLocation, . , , , 10 ( 10 ). ? , "". , . , , , . , , . , CoreLocation. , . , .
. , . , (, ), , 0 . , , .
" ". . , , , , CLLocation, , . ( ) .
, ARKit.

, .

( 3-4 ) , .

JS, AR CoreLocation.

— gravity worldAlignment . , . .
بدلا من الاستنتاج
Slack, , , , . AR. . AR AppStore 2017 . , .
روابط مفيدة
, . .