كيف قمنا بتطوير تطبيق AR لمراجعة الأماكن التاريخية



في الآونة الأخيرة ، قمنا بدمج التقنيات القديمة مع التقنيات الحديثة ، ما جاء منها يقرأ تحت القطع.

الواقع المعزز


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

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

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

المشكلة 1. النقاط العائمة


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

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

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

كما اتضح ، واجه العديد من الأخطاء هذه المكتبة ، ولكن لم يتم العثور على حل حتى الآن. للأسف ، لم يتم تحديث الرمز على GitHub لأكثر من ستة أشهر ، لذلك كان علي تجاوزه.

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

ونتيجة لذلك ، تقرر التخلي عن مكتبة ARKit-CoreLocation ووضع النقاط في الفضاء بمفردها. ساعد المقال ARKit و CoreLocation ، الذي كتبه كريستوفر ويب أورنشتاين ، كثيرًا في هذا. اضطررت إلى قضاء المزيد من الوقت وتحديث بعض الجوانب الرياضية في ذاكرتي ، ولكن النتيجة كانت تستحق العناء: كانت الكائنات AR في النهاية في أماكنها. بعد ذلك ، يبقى فقط تبعثرها على طول المحور Y بحيث تكون التسميات والنقاط أسهل في القراءة ، وإنشاء مراسلات بين المسافة من الموضع الحالي إلى النقطة والإحداثيات Z لكائن AR ، بحيث تكون المعلومات حول أقرب الأماكن التاريخية في المقدمة.

كان من الضروري حساب موضع SCNNode الجديد في الفضاء ، مع التركيز على الإحداثيات:

let place = PlaceNode() let locationTransform = MatrixHelper.transformMatrix(for: matrix_identity_float4x4, originLocation: curUserLocation, location: nodeLocation, yPosition: pin.yPos, shouldScaleByDistance: false) let nodeAnchor = ARAnchor(transform: locationTransform) scene.session.add(anchor: nodeAnchor) scene.scene.rootNode.addChildNode(place) 

تم إضافة الوظائف التالية إلى فئة MatrixHelper:

 class MatrixHelper { static func transformMatrix(for matrix: simd_float4x4, originLocation: CLLocation, location: CLLocation, yPosition: Float) -> simd_float4x4 { let distanceToPoint = Float(location.distance(from: originLocation)) let distanceToNode = (10 + distanceToPoint/1000.0) let bearing = GLKMathDegreesToRadians(Float(originLocation.coordinate.direction(to: location.coordinate))) let position = vector_float4(0.0, yPosition, -distanceToNode, 0.0) let translationMatrix = MatrixHelper.translationMatrix(with: matrix_identity_float4x4, for: position) let rotationMatrix = MatrixHelper.rotateAroundY(with: matrix_identity_float4x4, for: bearing) let transformMatrix = simd_mul(rotationMatrix, translationMatrix) return simd_mul(matrix, transformMatrix) } static func translationMatrix(with matrix: matrix_float4x4, for translation : vector_float4) -> matrix_float4x4 { var matrix = matrix matrix.columns.3 = translation return matrix } static func rotateAroundY(with matrix: matrix_float4x4, for degrees: Float) -> matrix_float4x4 { var matrix : matrix_float4x4 = matrix matrix.columns.0.x = cos(degrees) matrix.columns.0.z = -sin(degrees) matrix.columns.2.x = sin(degrees) matrix.columns.2.z = cos(degrees) return matrix.inverse } } 

لحساب السمت أضاف الملحق CLLocationCoordinate2D

 extension CLLocationCoordinate2D { func calculateBearing(to coordinate: CLLocationCoordinate2D) -> Double { let a = sin(coordinate.longitude.toRadians() - longitude.toRadians()) * cos(coordinate.latitude.toRadians()) let b = cos(latitude.toRadians()) * sin(coordinate.latitude.toRadians()) - sin(latitude.toRadians()) * cos(coordinate.latitude.toRadians()) * cos(coordinate.longitude.toRadians() - longitude.toRadians()) return atan2(a, b) } func direction(to coordinate: CLLocationCoordinate2D) -> CLLocationDirection { return self.calculateBearing(to: coordinate).toDegrees() } } 

المشكلة 2. كائنات AR الزائدة


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

ومع ذلك ، في بعض المناطق ، لا يزال تركيز النقاط مرتفعًا جدًا. لذلك ، لزيادة الرؤية ، قرروا استخدام التكتل. على شاشة الخريطة ، تتوفر هذه الميزة بشكل افتراضي بسبب المنطق المضمن في MapKit ، ولكن في وضع AR كان من الضروري تنفيذها يدويًا.

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

الصورة

لضمان تفاعل كائنات الواقع المعزز ، تم تعليق UITapGestureRecognizer على ARSCNView وفي المعالج باستخدام طريقة hitTest ، تحقق من أي من كائنات SCNNode التي نقر عليها المستخدم. إذا تبين أنها صورة لأماكن الجذب القريبة ، فتح التطبيق الألبوم المقابل في وضع ملء الشاشة.

مشكلة 3. الرادار


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



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

 func place(dot: Dot) { var y: CGFloat = 0.0 var x: CGFloat = 0.0 if degree < 0 { degree += 360 } let bearing = dot.bearing.toRadians() let radius: CGFloat = 60.0 // radius of the radar view if (bearing > 0 && bearing < .pi / 2) { //the 1 quadrant of the radar x = radius + CGFloat(cosf(Float((.pi / 2) - bearing)) * Float(dot.distance)) y = radius - CGFloat(sinf(Float((.pi / 2) - bearing)) * Float(dot.distance)) } else if (bearing > .pi / 2.0 && bearing < .pi) { //the 2 quadrant of the radar x = radius + CGFloat(cosf(Float(bearing - (.pi / 2))) * Float(dot.distance)) y = radius + CGFloat(sinf(Float(bearing - (.pi / 2))) * Float(dot.distance)) } else if (bearing > .pi && bearing < (3 * .pi / 2)) { //the 3 quadrant of the radar x = radius - CGFloat(cosf(Float((3 * .pi / 2) - bearing)) * Float(dot.distance)) y = radius + CGFloat(sinf(Float((3 * .pi / 2) - bearing)) * Float(dot.distance)) } else if (bearing > (3 * .pi / 2.0) && bearing < (2 * .pi)) { //the 4 quadrant of the radar x = radius - CGFloat(cosf(Float(bearing - (3 * .pi / 2))) * Float(dot.distance)) y = radius - CGFloat(sinf(Float(bearing - (3 * .pi / 2))) * Float(dot.distance)) } else if (bearing == 0) { x = radius y = radius - CGFloat(dot.distance) } else if (bearing == .pi / 2) { x = radius + CGFloat(dot.distance) y = radius } else if (bearing == .pi) { x = radius y = radius + CGFloat(dot.distance) } else if (bearing == 3 * .pi / 2) { x = radius - CGFloat(dot.distance) y = radius } else { x = radius y = radius - CGFloat(dot.distance) } let newPosition = CGPoint(x: x, y: y) dot.layer.position = newPosition 

الخلفية


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


في وقت تطوير تطبيق الهاتف المحمول ، كان جميع bekenders يشاركون في مشاريع تجارية ، ويسمح للمحتوى بتوفير عدة ساعات:

  • مطور المحمول - الخلفية مريحة
  • مدير المحتوى - منطقة إدارية ملائمة لملء البيانات

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

الخلاصة



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

بسيط ، للوهلة الأولى ، تطلب المشروع الكثير من ساعات العمل لتنفيذ وتحسين الخوارزميات ، على الرغم من حقيقة أننا استخدمنا SDK القياسي من Apple.

لقد نشرنا التطبيق مؤخرًا في AppStore . إليك ما يبدو عليه في العمل.


حتى الآن ، في قاعدة بياناتنا هناك نقاط فقط لـ Taganrog ، ومع ذلك ، يمكن للجميع المشاركة في توسيع "منطقة التغطية".

Source: https://habr.com/ru/post/ar421067/


All Articles