تتيح لك Hit Test API وضع عناصر افتراضية في عرض العالم الحقيقي.
تم طرح WebXR Device API في الخريف الماضي في الإصدار 79 من Chrome. كما ذكرنا سابقًا، لا يزال تنفيذ واجهة برمجة التطبيقات في Chrome قيد التطوير. يسرّ Chrome أن يعلن عن الانتهاء من بعض المهام. في الإصدار 81 من Chrome، تم طرح ميزتَين جديدتَين:
تتناول هذه المقالة WebXR Hit Test API، وهي وسيلة لوضع كائنات افتراضية في عرض الكاميرا في العالم الحقيقي.
في هذه المقالة، سأفترض أنّك تعرف كيفية إنشاء جلسة واقع معزّز وكيفية تشغيل حلقة إطارات. إذا لم تكن على دراية بهذه المفاهيم، ننصحك بقراءة المقالات السابقة ضمن هذه السلسلة.
- الواقع الافتراضي على الويب
- الواقع الافتراضي على الويب، الجزء الثاني
- الواقع المعزّز على الويب: قد تكون على دراية بطريقة استخدامه
عيّنة لجلسة الواقع المعزّز الغامر
يستند الرمز البرمجي الوارد في هذه المقالة إلى الرمز البرمجي المتوفّر في عيّنة اختبار النتائج من مجموعة عمل Immersive Web، ولكنّه ليس مطابقًا له (العرض التوضيحي، المصدر). يتيح لك هذا المثال وضع زهور دوّار الشمس الافتراضية على الأسطح في العالم الحقيقي.
عند فتح التطبيق لأول مرة، ستظهر لك دائرة زرقاء تحتوي على نقطة في المنتصف. النقطة هي نقطة التقاطع بين خط وهمي من جهازك إلى النقطة في البيئة. ويتحرّك هذا الشكل أثناء تحريك الجهاز. وعندما يعثر على نقاط تقاطع، يبدو أنّه يلتصق بالأسطح، مثل الأرضيات وأسطح الطاولات والجدران. ويحدث ذلك لأنّ اختبار التداخل يوفّر موضع نقطة التقاطع واتجاهها، ولكنّه لا يوفّر أي معلومات عن الأسطح نفسها.
تُسمّى هذه الدائرة شبكة تصويب، وهي صورة مؤقتة تساعد في وضع عنصر في الواقع المعزّز. إذا نقرت على الشاشة، سيتم وضع زهرة عباد الشمس على السطح في موقع علامة التصويب واتجاه نقطة علامة التصويب، بغض النظر عن المكان الذي نقرت فيه على الشاشة. سيستمر تحرّك الشبكة مع جهازك.
إنشاء شبكة الأسلاك
يجب إنشاء صورة شبكة التصويب بنفسك لأنّ المتصفّح أو واجهة برمجة التطبيقات لا يوفّرانها. تختلف طريقة تحميلها ورسمها حسب إطار العمل.
إذا لم تكن ترسمها مباشرةً باستخدام WebGL أو WebGL2، راجِع مستندات إطار العمل. لهذا السبب، لن أتطرّق إلى تفاصيل حول كيفية رسم شبكة التصويب في العيّنة. أعرض أدناه سطرًا واحدًا منه لسبب واحد فقط، وهو أن تعرف في نماذج الرموز اللاحقة ما أشير إليه عند استخدام المتغيّر reticle.
let reticle = new Gltf2Node({url: 'media/gltf/reticle/reticle.gltf'});
طلب جلسة
عند طلب جلسة، يجب طلب 'hit-test' في مصفوفة requiredFeatures كما هو موضّح أدناه.
navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['local', 'hit-test']
})
.then((session) => {
// Do something with the session
});
الدخول إلى جلسة
في المقالات السابقة، قدّمتُ رمزًا برمجيًا لبدء جلسة XR. لقد أدرجتُ أدناه نسخة من هذا النص مع بعض الإضافات. أولاً، أضفتُ معالج الأحداث select. عندما ينقر المستخدم على الشاشة، سيتم وضع زهرة في عرض الكاميرا استنادًا إلى وضع علامة التصويب. سأشرح أداة معالجة الأحداث هذه لاحقًا.
function onSessionStarted(xrSession) {
xrSession.addEventListener('end', onSessionEnded);
xrSession.addEventListener('select', onSelect);
let canvas = document.createElement('canvas');
gl = canvas.getContext('webgl', { xrCompatible: true });
xrSession.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});
xrSession.requestReferenceSpace('viewer').then((refSpace) => {
xrViewerSpace = refSpace;
xrSession.requestHitTestSource({ space: xrViewerSpace })
.then((hitTestSource) => {
xrHitTestSource = hitTestSource;
});
});
xrSession.requestReferenceSpace('local').then((refSpace) => {
xrRefSpace = refSpace;
xrSession.requestAnimationFrame(onXRFrame);
});
}
مساحات مرجعية متعددة
لاحظ أنّ الرمز المميّز ينفّذ الدالة XRSession.requestReferenceSpace() مرتين. في البداية، بدا لي هذا الأمر مربكًا. سألتُ عن سبب عدم طلب رمز اختبار النقر على إطار متحرك (بدء حلقة الإطار) وعن سبب عدم تضمّن حلقة الإطار اختبارات النقر. كان سبب الالتباس هو سوء فهم لمساحات المراجع. تعرض مساحات المراجع العلاقات بين نقطة الأصل والعالم.
لفهم ما يفعله هذا الرمز، تخيَّل أنّك تشاهد هذا المثال باستخدام جهاز مستقل، وأنّ لديك سماعة رأس ووحدة تحكّم. لقياس المسافات من وحدة التحكّم، عليك استخدام إطار مرجعي مركّز على وحدة التحكّم. ولكن لرسم شيء على الشاشة، عليك استخدام إحداثيات تركّز على المستخدم.
في هذا المثال، يكون المشاهد والجهاز المتحكّم هما الجهاز نفسه. ولكن لدي مشكلة. يجب أن يكون ما أرسمه ثابتًا بالنسبة إلى البيئة المحيطة، ولكن "وحدة التحكّم" التي أرسم بها تتحرك.
لرسم الصور، أستخدم مساحة مرجعية local تمنحني ثباتًا من حيث البيئة. بعد الحصول على هذا الإذن، أبدأ حلقة الإطارات من خلال استدعاء requestAnimationFrame().
بالنسبة إلى اختبارات التفاعل، أستخدم viewer مساحة مرجعية تستند إلى وضع الجهاز في وقت إجراء اختبار التفاعل. إنّ التصنيف "المشاهد" مربك بعض الشيء في هذا السياق لأنّني أتحدث عن وحدة تحكّم. ويكون ذلك منطقيًا إذا اعتبرت وحدة التحكّم بمثابة جهاز عرض إلكتروني. بعد الحصول على هذا الإذن، أتصل بـ xrSession.requestHitTestSource()، ما يؤدي إلى إنشاء مصدر بيانات اختبار النقر الذي سأستخدمه عند الرسم.
تشغيل حلقة إطارات
يتلقّى requestAnimationFrame() رد الاتصال أيضًا رمزًا جديدًا للتعامل مع اختبارات النتائج.
أثناء تحريك جهازك، يجب أن تتحرّك شبكة التصويب معه أثناء محاولتها العثور على أسطح. لإضفاء لمسة ديناميكية، أعِد رسم شبكة التصويب في كل إطار.
ولكن لا تعرِض شبكة التصويب إذا تعذّر اختبار الضغط. لذا، بالنسبة إلى شبكة التصويب التي أنشأتها سابقًا، ضبطتُ السمة visible على false.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
لرسم أي شيء في الواقع المعزّز، أحتاج إلى معرفة مكان المشاهد والمكان الذي ينظر إليه. لذلك، أختبر ما إذا كان hitTestSource وxrViewerPose لا يزالان صالحَين.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
سأطلب الآن من "getHitTestResults()" إجراء المكالمة. تأخذ هذه الدالة hitTestSource كوسيطة
وتعرض صفيفًا من مثيلات HitTestResult. قد يعثر اختبار التحديد على أسطح متعددة. ويكون العنصر الأول في الصفيف هو الأقرب إلى الكاميرا.
ستستخدم هذه السمة في معظم الأوقات، ولكن سيتم عرض مصفوفة لحالات الاستخدام المتقدّمة. على سبيل المثال، لنفترض أنّ الكاميرا موجّهة إلى صندوق على طاولة على أرضية. من المحتمل أن يعرض اختبار تحديد المواقع الجغرافية جميع مساحات العرض الثلاث في المصفوفة. في معظم الحالات، سيكون هذا هو المربّع الذي يهمني. إذا كان طول المصفوفة التي تم عرضها هو 0، أي إذا لم يتم عرض أي اختبار إصابة، يمكنك المتابعة. يُرجى إعادة المحاولة في الإطار التالي.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.visible = true;
reticle.matrix = pose.transform.matrix;
}
}
// Draw to the screen
}
أخيرًا، عليّ معالجة نتائج اختبار التصادم. وتتمثّل العملية الأساسية فيما يلي. احصل على وضعية من نتيجة اختبار التحديد، ثم غيِّر موضع صورة شبكة التصويب (حرِّكها) إلى موضع اختبار التحديد، ثم اضبط قيمة السمة visible على "صحيح". يمثّل الوضع
موضع نقطة على سطح.
function onXRFrame(hrTime, xrFrame) {
let xrSession = xrFrame.session;
xrSession.requestAnimationFrame(onXRFrame);
let xrViewerPose = xrFrame.getViewerPose(xrRefSpace);
reticle.visible = false;
// Reminder: the hitTestSource was acquired during onSessionStart()
if (xrHitTestSource && xrViewerPose) {
let hitTestResults = xrFrame.getHitTestResults(xrHitTestSource);
if (hitTestResults.length > 0) {
let pose = hitTestResults[0].getPose(xrRefSpace);
reticle.matrix = pose.transform.matrix;
reticle.visible = true;
}
}
// Draw to the screen
}
وضع عنصر
يتم وضع عنصر في الواقع المعزّز عندما ينقر المستخدم على الشاشة. لقد أضفتُ معالج أحداث select إلى الجلسة. (راجِع ما ورد أعلاه.)
الأمر المهم في هذه الخطوة هو معرفة مكان وضعها. بما أنّ شبكة التصويب المتحرّكة توفّر مصدرًا ثابتًا لاختبارات تحديد المواقع، فإنّ أبسط طريقة لوضع عنصر هي رسمه في موقع شبكة التصويب في آخر اختبار تحديد موقع.
function onSelect(event) {
if (reticle.visible) {
// The reticle should already be positioned at the latest hit point,
// so we can just use its matrix to save an unnecessary call to
// event.frame.getHitTestResults.
addARObjectAt(reticle.matrix);
}
}
الخاتمة
وأفضل طريقة للتعرّف على كيفية استخدامها هي الاطّلاع على نموذج الرمز البرمجي أو تجربة التدريب العملي. آمل أن أكون قد قدّمت لك معلومات أساسية كافية لفهم كليهما.
لم ننتهِ بعد من إنشاء واجهات برمجة تطبيقات للويب التفاعلي، بل لا يزال أمامنا الكثير. سننشر مقالات جديدة هنا كلما أحرزنا تقدمًا.
صورة من دانيال فرانك على Unsplash