إنشاء تجارب الخرائط ثلاثية الأبعاد باستخدام عرض WebGL المركب

1. قبل البدء

يعلّمك هذا الدرس التطبيقي حول كيفية استخدام الميزات المستندة إلى WebGL في واجهة برمجة تطبيقات JavaScript ل"خرائط Google" للتحكّم على الخريطة الموجّهة وعرضها في ثلاثة أبعاد.

دبوس ثلاثي الأبعاد النهائي

المتطلّبات الأساسية

يفترض هذا الدرس التطبيقي حول الترميز أن لديك معرفة متوسطة بلغة JavaScript وواجهة برمجة تطبيقات JavaScript للخرائط. للتعرُّف على أساسيات استخدام واجهة برمجة تطبيقات JavaScript JS، جرّب الدرس التطبيقي حول إضافة خريطة إلى موقعك الإلكتروني (JavaScript).

ما ستتعرَّف عليه

  • إنشاء رقم تعريف خريطة مع تفعيل الخريطة المتجهة لـ JavaScript.
  • التحكم في الخريطة من خلال الإمالة والتدوير الآليين.
  • عرض العناصر الثلاثية الأبعاد على الخريطة باستخدام WebGLOverlayView وthree.js.
  • صورة متحركة لحركات الكاميرا باستخدام moveCamera.

الأشياء التي تحتاج إليها

  • حساب Google Cloud Platform تم تفعيل الفوترة به
  • مفتاح API لمنصة "خرائط Google" مع تفعيل واجهة برمجة تطبيقات JavaScript ل"خرائط Google"
  • معرفة متوسطة بجافا سكريبت وHTML وCSS
  • محرِّر نصوص أو IDE من اختيارك
  • Node.js

2. الإعداد

بالنسبة إلى خطوة التفعيل أدناه، عليك تفعيل واجهة برمجة تطبيقات JavaScript للخرائط.

إعداد "منصة خرائط Google"

إذا لم يكن لديك حساب على Google Cloud Platform ومشروع تم تفعيل الفوترة فيه، يُرجى الاطّلاع على دليل بدء استخدام "منصة خرائط Google" لإنشاء حساب فوترة ومشروع.

  1. في Cloud Console، انقر على القائمة المنسدلة للمشروع واختَر المشروع الذي تريد استخدامه لهذا الدرس التطبيقي.

  1. فعِّل واجهات برمجة تطبيقات ومنصة SDK لمنصة "خرائط Google" المطلوبة لهذا الدرس التطبيقي في Google Cloud Marketplace. ولإجراء ذلك، اتّبِع الخطوات الواردة في هذا الفيديو أو هذه المستندات.
  2. يمكنك إنشاء مفتاح واجهة برمجة تطبيقات في صفحة بيانات الاعتماد في Cloud Console. يمكنك اتّباع الخطوات الواردة في هذا الفيديو أو هذه المستندات. تتطلب جميع الطلبات إلى "منصة خرائط Google" مفتاح واجهة برمجة تطبيقات.

إعداد Node.js

وإذا لم يتوفّر لديك ذلك، يمكنك الانتقال إلى https://nodejs.org/ لتنزيل وقت تشغيل Node.js وتثبيته على جهاز الكمبيوتر.

يتوفّر برنامج Node.js مع مدير حزمة npm، والذي تحتاج إلى تثبيت تبعيات لهذا الدرس التطبيقي حول الترميز.

تنزيل نموذج بدء تشغيل المشروع

قبل بدء هذا الدرس التطبيقي، يُرجى اتّباع الخطوات التالية لتنزيل نموذج المشروع للمبتدئين، بالإضافة إلى رمز الحلّ الكامل:

  1. نزِّل إعادة اختبار GitHub أو تشعّبها لهذا الدرس التطبيقي على https://github.com/googlecodelabs/maps-platform-101-webgl/. يقع مشروع المبتدئين في الدليل /starter ويشمل بنية الملف الأساسية التي تحتاجها لإكمال الدرس التطبيقي حول الترميز. ستجد كل ما تحتاج إليه للعمل في دليل /starter/src.
  2. بعد تنزيل مشروع التفعيل، يمكنك تشغيل npm install في الدليل /starter. يؤدي ذلك إلى تثبيت جميع التبعيات المطلوبة المدرجة في package.json.
  3. بعد تثبيت تبعيات الأجهزة، يمكنك تشغيل npm start في الدليل.

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

إذا كنت تريد عرض رمز الحل بالكامل، يمكنك إكمال خطوات الإعداد أعلاه في دليل /solution.

إضافة مفتاح واجهة برمجة التطبيقات

يتضمن تطبيق المبتدئين كل الرموز اللازمة لتحميل الخريطة باستخدام برنامج تحميل واجهة برمجة تطبيقات جافا سكريبت، بحيث كل ما تحتاج إليه هو تقديم مفتاح واجهة برمجة التطبيقات ورقم تعريف الخريطة. أداة التحميل JS API هي مكتبة بسيطة تزيل الطريقة التقليدية لتحميل واجهة برمجة تطبيقات JS API المضمنة في نموذج HTML بعلامة script، مما يسمح لك بالتعامل مع كل شيء في رمز JavaScript.

لإضافة مفتاح واجهة برمجة التطبيقات، يجب تنفيذ ما يلي في مشروع إجراء التفعيل:

  1. فتح app.js
  2. في الكائن apiOptions، اضبط مفتاح واجهة برمجة التطبيقات كقيمة apiOptions.apiKey.

3- إنشاء واستخدام رقم تعريف الخريطة

لاستخدام الميزات المستندة إلى WebGL في واجهة برمجة تطبيقات JavaScript ل"خرائط Google"، تحتاج إلى رقم تعريف على الخريطة تم تفعيل ربط المتّجه إليه.

جارٍ إنشاء رقم تعريف للخريطة

إنشاء رقم تعريف الخريطة

  1. في Google Cloud Console، انتقِل إلى "منصة خرائط Google" ' > "إدارة الخرائط&#39؛.
  2. انقر على "إنشاء معرّف خريطة جديد"&#39؛
  3. في حقل "اسم الخريطة"، انقر على اسم لرقم تعريف الخريطة.
  4. في القائمة المنسدلة "نوع الخريطة"، انقر على "JavaScript&#39". ستظهر خيارات جافا سكريبت&#39؛.
  5. ضمن "خيارات جافا سكريبت &#39؛؛" اختَر زر الاختيار "Vector&#39"، ومربّع الاختيار "إمالة&#39" ومربع التدوير.
  6. Optional. في حقل "الوصف&39"، أدخِل وصفًا لمفتاح واجهة برمجة التطبيقات.
  7. انقر على الزر "التالي&#39؛ ستظهر صفحة "تفاصيل رقم تعريف الخريطة، رقم 39".

    صفحة تفاصيل الخريطة
  8. انسخ رقم تعريف الخريطة. ستستخدم هذا في الخطوة التالية لتحميل الخريطة.

استخدام رقم تعريف الخريطة

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

لتحميل الخريطة باستخدام رقم تعريف الخريطة، يمكنك إجراء ما يلي:

  1. اضبط رقم تعريف الخريطة كقيمة mapOptions.mapId.

    يؤدي تقديم رقم تعريف الخريطة عند إنشاء خريطة إلى تزويد Google Maps Platform بخرائطك التي سيتم تحميلها لمثيل معين. ويمكنك إعادة استخدام رقم تعريف الخريطة نفسه في تطبيقات متعددة أو ملفات شخصية متعددة داخل التطبيق نفسه.
    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    };
    

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

إذا لم يتم تحميل الخريطة، تأكّد من تقديم مفتاح صالح لواجهة برمجة التطبيقات في apiOptions. إذا لم تتم إمالة الخريطة وتدويرها، تأكد من تقديم معرّف خريطة مع تفعيل الإمالة والتدوير في apiOptions وmapOptions.

خريطة مائلة

من المفترض أن يظهر ملف app.js على النحو التالي:

    import { Loader } from '@googlemaps/js-api-loader';

    const apiOptions = {
      "apiKey": 'YOUR_API_KEY',
    };

    const mapOptions = {
      "tilt": 0,
      "heading": 0,
      "zoom": 18,
      "center": { lat: 35.6594945, lng: 139.6999859 },
      "mapId": "YOUR_MAP_ID"
    }

    async function initMap() {
      const mapDiv = document.getElementById("map");
      const apiLoader = new Loader(apiOptions);
      await apiLoader.load();
      return new google.maps.Map(mapDiv, mapOptions);
    }

    function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      // WebGLOverlayView code goes here
    }

    (async () => {
      const map = await initMap();
    })();

4. تنفيذ WebGLOverlayView

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

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

  • onAdd(): يتم طلب هذا عند إضافة التراكب إلى خريطة عن طريق استدعاء setMap في مثيل WebGLOverlayView. وهذا هو الموضع الذي عليك فيه تنفيذ أي عمل ذي صلة بـ WebGL ولا يتطلب الدخول المباشر إلى سياق WebGL.
  • onContextRestored(): يتم استدعاء عندما يصبح سياق WebGL متاحًا ولكن قبل عرض أي عنصر. وفي هذه الحالة، يجب إعداد العناصر وحالة الربط وتنفيذ أي إجراء آخر يحتاج إلى الوصول إلى سياق WebGL ولكن يمكن تنفيذه خارج استدعاء onDraw(). ويسمح لك ذلك بإعداد كل ما تحتاجه بدون إضافة حمل زائد على العرض الفعلي للخريطة، وهو ما يستهلك الكثير من وحدة معالجة الرسومات.
  • onDraw(): يتم الاتصال مرة واحدة لكل إطار بعد بدء WebGL بعرض الخريطة وأي شيء آخر طلبته. يجب أن تبذل أقل قدر ممكن من الجهود في onDraw() لتجنب حدوث مشكلة في الأداء في عرض الخريطة.
  • onContextLost(): يتم طلب ذلك عند فقدان سياق عرض WebGL لأي سبب.
  • onRemove(): يتم طلب هذا عند إزالة التراكب من الخريطة من خلال استدعاء setMap(null) في مثيل WebGLOverlayView.

في هذه الخطوة، ستنشئ مثيلًا لـ WebGLOverlayView وتنفّذ ثلاث علامات تبويب دورة الحياة: onAdd وonContextRestored وonDraw. للحفاظ على تنظيم العناصر وسهولة متابعته، سيتم التعامل مع كل رموز التراكب في دالة initWebGLOverlayView() المُقدَّمة في نموذج إجراء التفعيل لهذا الدرس التطبيقي حول الترميز.

  1. إنشاء مثيل WebGLOverlayView().

    يتم توفير التراكب بواسطة واجهة برمجة تطبيقات JavaScript JS في google.maps.WebGLOverlayView. للبدء، أنشئ مثيلاً من خلال انتظار ما يلي إلى initWebGLOverlayView():
    const webGLOverlayView = new google.maps.WebGLOverlayView();
    
  2. تنفيذ الخطافات المناسبة لدورة الحياة.

    تنفيذ العناصر التي تتم إضافتها في دورة الحياة، أضِف ما يلي إلى initWebGLOverlayView():
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {};
    
  3. إضافة مثيل التراكب إلى الخريطة.

    يمكنك الآن استدعاء تطبيق setMap() على مثيل التراكب وتمريره في الخريطة من خلال إلحاق ما يلي بـ initWebGLOverlayView():
    webGLOverlayView.setMap(map)
    
  4. تواصل هاتفيًا مع "initWebGLOverlayView".

    الخطوة الأخيرة هي تنفيذ initWebGLOverlayView() من خلال إضافة ما يلي إلى الدالة التي تم استدعاؤها على الفور في أسفل app.js:
    initWebGLOverlayView(map);
    

من المفترض أن تبدو الدالة initWebGLOverlayView والاستدعاء المُفعَّل على الفور بهذا الشكل:

    async function initWebGLOverlayView (map) {
      let scene, renderer, camera, loader;
      const webGLOverlayView = new google.maps.WebGLOverlayView();

      webGLOverlayView.onAdd = () => {}
      webGLOverlayView.onContextRestored = ({gl}) => {}
      webGLOverlayView.onDraw = ({gl, coordinateTransformer}) => {}
      webGLOverlayView.setMap(map);
    }

    (async () => {
      const map = await initMap();
      initWebGLOverlayView(map);
    })();

هذا كل ما تحتاج إليه لتنفيذ WebGLOverlayView. بعد ذلك، سيكون عليك إعداد كل ما تحتاج إليه لعرض كائن ثلاثي الأبعاد على الخريطة باستخدام Three.js.

5. إعداد مشهد الثلاث.js

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

هناك ثلاثة أنواع أساسية من العناصر في Three.js مطلوبة لعرض أي شيء:

  • المشهد: A "container" حيث يتم عرض جميع العناصر والمصادر الخفيفة والزخارف وما إلى ذلك.
  • الكاميرا: كاميرا تمثل نقطة المنظر تتوفّر أنواع متعددة من الكاميرات، ويمكن إضافة كاميرا واحدة أو أكثر إلى مشهد واحد.
  • العارض: عارض يتعامل مع معالجة جميع العناصر في الموقع وعرضها. في Three.js، يكون WebGLRenderer هو الأكثر استخدامًا، ولكن يتوفر البعض الآخر كقيم احتياطية في حال كان البرنامج لا يدعم WebGL.

في هذه الخطوة، ستتمكّن من تحميل جميع تبعيات ملف 3.js المطلوبة وإعداد مشهد أساسي.

  1. تحميل three.js

    ستحتاج إلى تبعيَّتين لهذا الدرس التطبيقي، هما مكتبة Three.js وGLTF Loader، وهي فئة تتيح لك تحميل عناصر ثلاثية الأبعاد بتنسيق GL Trasmission (gLTF). يوفر Three.js أدوات تحميل متخصصة للعديد من تنسيقات الكائنات الثلاثية الأبعاد المختلفة ولكن يُنصَح باستخدام gLTF.

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

    لاستيراد Three.js وGLTF Loader، أضِف ما يلي إلى أعلى app.js:
    import * as THREE from 'three';
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js';
    
  2. أنشِئ مشهدًا بتنسيق ثلاثة.js.

    لإنشاء مشهد، يمكنك إنشاء فئة Scene.js من خلال إلحاق ما يلي بخطاف onAdd:
    scene = new THREE.Scene();
    
  3. أضف كاميرا إلى المشهد.

    كما ذكرنا سابقًا، تمثّل الكاميرا منظور المنظر في المشهد، وتحدّد طريقة تعامل Three.js مع العرض المرئي للعناصر في المشهد. وما مِن كاميرا في الواقع، لا يتم عرض المشهد في الواقع، ما يعني أن المحتوى لن يظهر لأنه لن يتم عرضه.

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

    لإضافة كاميرا منظور إلى المشهد، يمكنك إلحاق ما يلي بالخطّاط في onAdd:
    camera = new THREE.PerspectiveCamera();
    
    باستخدام PerspectiveCamera، يمكنك أيضًا ضبط السمات التي تشكّل نقطة المشاهدة، بما في ذلك الطائرات القريبة والبعيدة ونسبة العرض إلى الارتفاع ومجال الرؤية (الرنين). تشكّل هذه السمات إجمالاً ما يُعرف باسم ال إنجاز، وهو مفهوم مهم يجب استيعابه عند العمل بتقنية ثلاثية الأبعاد، ولكن خارج نطاق هذا الدرس التطبيقي حول الترميز. يكفي ضبط PerspectiveCamera التلقائي.
  4. أضِف مصادر إضاءة إلى المشهد.

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

    يوفر Three.js مجموعة متنوعة من أنواع الإضاءة المختلفة التي ستستخدمها:

  5. AmbientLight: لتوفير مصدر إضاءة منتشر في جميع العناصر في الصورة من جميع الزوايا بالتساوي. سيوفّر هذا الإجراء مقدارًا أساسيًا من الإضاءة لضمان ظهور الزخارف على كل العناصر.
  6. DirectionalLight: لتوفير ضوء ينشأ من اتجاه في المشهد. وعلى عكس كيفية عمل المصباح ذي الموضع الصحيح في العالم الحقيقي، تكون شعاعات الضوء التي تنبعث من DirectionalLight متوازية ولا تنتشر وتنتشر بعد بُعدها عن مصدر الضوء.

    يمكنك ضبط لون كل كثافة وكثافتها لإنشاء تأثيرات إضاءة مجمّعة. على سبيل المثال، في الرمز أدناه، يقدّم الضوء المحيط ضوء أبيض ناعم للمشهد بأكمله، بينما يقدّم المصباح الاتجاهي ضوءًا ثانويًا يُصدِم أجسامًا بزاوية منخفضة. في حالة ضوء الاتجاه، يتم ضبط الزاوية باستخدام position.set(x, y ,z)، حيث ترتبط كل قيمة بالمحور ذي الصلة. على سبيل المثال، على سبيل المثال، ستضع position.set(0,1,0) الضوء فوق المشهد مباشرةً على المحور الصادي الذي يشير مباشرةً إلى الأسفل.

    لإضافة مصادر الضوء إلى المشهد، يمكنك إلحاق ما يلي بخطاف onAdd:
    const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
    directionalLight.position.set(0.5, -1, 0.5);
    scene.add(directionalLight);
    

من المفترض أن يبدو الخطاف في onAdd الآن على النحو التالي:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 );
      scene.add(ambientLight);
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);
    }

تم إعداد المشهد الآن وأصبح جاهزًا للعرض. بعد ذلك، سيكون عليك ضبط عارض WebGL وعرض المشهد.

6- عرض المشهد

حان الوقت لعرض المشهد. حتى الآن، يتم إعداد كل ما أنشأته باستخدام Three.js في الترميز، إلا أنه غير موجود في الأساس لأنه لم يتم عرضه بعد في سياق عرض WebGL. يعرض WebGL محتوى ثنائي الأبعاد وثلاثي الأبعاد في المتصفح باستخدام واجهة برمجة تطبيقات اللوحة. إذا كنت قد استخدمت واجهة برمجة تطبيقات"لوحة الرسم"من قبل، ربما تكون معتادًا على context من لوحة رسم HTML، حيث يتم عرض كل شيء. ما لا تعرفه هو أن هذه هي واجهة تعرض سياق عرض رسومات OpenGL عبر واجهة برمجة تطبيقات WebGLRenderingContext في المتصفّح.

لتسهيل التعامل مع عارض WebGL، يوفر Three.js WebGLRenderer، وهو برنامج تضمين يسهِّل نسبيًا إعداد سياق عرض WebGL بحيث يمكن لـ Three.js عرض مشاهد في المتصفح. ولكن في حالة الخريطة، لا يكفي عرض مشهد Three.js في المتصفح إلى جانب الخريطة. يجب أن يُعرض Three.js في سياق العرض نفسه الموجود على الخريطة، بحيث يتم عرض كل من الخريطة وأي كائنات من مشهد Three.js في نفس العالم العالمي. ويتيح ذلك للعارض التعامل مع التفاعلات بين العناصر على الخريطة والكائنات في المشهد، مثل التراكم الذي يمثل تراكمًا في الخلفية أو تصغيرًا.

يبدو هذا معقدًا، أليس كذلك؟ لحسن الحظ، يأتي فريق Three.js للإنقاذ مرة أخرى.

  1. إعداد عارض WebGL.

    عند إنشاء مثيل جديد من WebGLRenderer.js الثلاثة، يمكنك توفير سياق عرض WebGL محدد تريد عرض المشهد عليه. هل تتذكر الوسيطة gl التي يتم تمريرها إلى هوك onContextRestored؟ عنصر gl هو سياق عرض WebGL على الخريطة. ما عليك سوى توفير السياق واللوحة وسماتها إلى مثيل WebGLRenderer، وكلها متوفّرة من خلال الكائن gl. وفي هذا الرمز، يتم أيضًا ضبط خاصية autoClear الخاصة بالعارض على false بحيث لا يمحو العارض أي نتائج في كل إطار.

    لإعداد عارض، يُرجى إلحاق ما يلي بخطاف onContextRestored:
    renderer = new THREE.WebGLRenderer({
      canvas: gl.canvas,
      context: gl,
      ...gl.getContextAttributes(),
    });
    renderer.autoClear = false;
    
  2. عرض المشهد

    بعد إعداد العارض، اطلب من requestRedraw على المثيل WebGLOverlayView إبلاغك بإعادة رسم الإطار عند عرض الإطار التالي، ثم استدعاء render على العارض وتمريره مشهد Three.js والكاميرا للعرض. وأخيرًا، عليك محو حالة سياق عرض WebGL. تُعد هذه خطوة مهمة لتجنب تعارض حالة GL، نظرًا لأن استخدام عرض تراكب WebGL يعتمد على حالة GL المشتركة. وإذا لم تتم إعادة ضبط الحالة في نهاية كل طلب سحب، قد تؤدي حالات GL للحالة إلى تعذّر العارض.

    ولإجراء ذلك، أضِف ما يلي إلى خطّاف onDraw حتى يتم تنفيذ كل إطار:
    webGLOverlayView.requestRedraw();
    renderer.render(scene, camera);
    renderer.resetState();
    

من المفترض أن تبدو الخطوط onContextRestored وonDraw الآن على النحو التالي:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

7- عرض نموذج ثلاثي الأبعاد على الخريطة

حسنًا، لديك كل القطع في مكانها الصحيح. لقد أعددت عرض تراكب WebGL وأنشأت مشهد Three.js، ولكن هناك مشكلة واحدة: لا يوجد شيء فيه. والآن، حان وقت عرض كائن ثلاثي الأبعاد في المشهد. ولإجراء ذلك، ستستخدِم "أداة تحميل GLTF" التي استوردتها سابقًا.

تتوفّر النماذج الثلاثية الأبعاد بتنسيقات مختلفة، لكن تنسيق gLTF هو التنسيق المفضّل لملف Three.js بسبب حجمه وحجم وقت التشغيل. في هذا الدرس التطبيقي حول الترميز، يتوفّر لك نموذج ليتم عرضه في المشهد في /src/pin.gltf.

  1. إنشاء مثيل عامل تحميل النموذج

    إضافة ما يلي إلى onAdd:
    loader = new GLTFLoader();
    
  2. حمِّل نموذجًا ثلاثي الأبعاد.

    أدوات تحميل النماذج غير متزامنة وتنفِّذ استدعاءً بعد تحميل النموذج بالكامل. لتحميل pin.gltf، أضِف ما يلي إلى onAdd:
    const source = "pin.gltf";
    loader.load(
      source,
      gltf => {}
    );
    
  3. أضِف النموذج إلى المشهد.

    يمكنك الآن إضافة النموذج إلى المشهد من خلال إلحاق ما يلي باستدعاء loader. لاحظ أنه تتم إضافة gltf.scene، وليس gltf:
    scene.add(gltf.scene);
    
  4. اضبط مصفوفة عرض الكاميرا.

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

    في حالة WebGLOverlayView، يتم استخدام مصفوفة العرض لإعلام العارض بمكان عرض مشهد Three.js وطريقة ارتباطه بالخريطة الأساسية. ولكن هناك مشكلة. يتم تحديد المواقع على الخريطة كأزواج إحداثيات خطوط العرض وخطوط الطول، بينما المواقع في مشهد Three.js هي إحداثيات Vector3. كما قد توقعت، لا يُعد حساب التحويل بين النظامين عملية بسيطة. لحل هذه المشكلة، يمرِّر WebGLOverlayView كائن coordinateTransformer إلى خطاف دورة الحياة OnDraw الذي يحتوي على دالة تُسمى fromLatLngAltitude. يستخدم fromLatLngAltitude كائن LatLngAltitude أو LatLngAltitudeLiteral، وبشكل اختياري، مجموعة من الوسيطات التي تحدد تحويلاً للمشهد، ثم تغطيها إلى مصفوفة إسقاط العرض (MVP) لك. ما عليك سوى تحديد المكان الذي تريد عرض مشهد Three.js عليه على الخريطة، بالإضافة إلى الطريقة التي تريد تحويله، وWebGLOverlayView ستتكفل بالباقي. ويمكنك بعد ذلك تحويل مصفوفة MVP إلى مصفوفة Matrix4 Three.js وضبط مصفوفة عرض الكاميرا عليها.

    في الرمز الوارد في ما يلي، تُطلب الوسيطة الثانية طريقة عرض WebGL المركّبة لضبط ارتفاع مشهد Three.js على ارتفاع 120 متر فوق سطح الأرض، الأمر الذي سيجعل النموذج يطفو.

    لضبط مصفوفة عرض الكاميرا، يمكنك إلحاق ما يلي بخطاف onDraw:
    const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 120
    }
    const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
    camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
    
  5. تحويل النموذج.

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

    في حالة هذا النموذج، لم يتم إنشاؤه باستخدام ما نعتبره عادةً "أعلى" و#39، ودبابيس مواجهة للمحور "ص"، لذا تحتاج إلى تحويل الكائن إلى الاتجاه الملائم للمساحة الفضائية من خلال طلب rotation.set عليه. لاحظ أنه في Three.js، يتم تحديد التدوير بوحدات الراديان وليس بالدرجات. يكون من السهل عمومًا التفكير بالدرجات، لذا يجب إجراء الإحالة الناجحة المناسبة باستخدام الصيغة degrees * Math.PI/180.

    بالإضافة إلى ذلك، فإن النموذج صغير بعض الشيء، وبالتالي يمكنك أيضًا تغيير حجمه بشكل متساوٍ على جميع المحاور عن طريق الاتصال بـ scale.set(x, y ,z).

    لتدوير النموذج وتغيير حجمه، أضِف ما يلي في استدعاء loader للسمة onAdd قبل scene.add(gltf.scene)، ما يضيف قيمة gLTF إلى المشهد:
    gltf.scene.scale.set(25,25,25);
    gltf.scene.rotation.x = 180 * Math.PI/180;
    

الآن، يستقر الدبوس بشكل عمودي على الخريطة.

دبوس رأسي

من المفترض أن تبدو الخطوط onAdd وonDraw الآن على النحو التالي:

    webGLOverlayView.onAdd = () => {
      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera();
      const ambientLight = new THREE.AmbientLight( 0xffffff, 0.75 ); // soft white light
      scene.add( ambientLight );
      const directionalLight = new THREE.DirectionalLight(0xffffff, 0.25);
      directionalLight.position.set(0.5, -1, 0.5);
      scene.add(directionalLight);

      loader = new GLTFLoader();
      const source = 'pin.gltf';
      loader.load(
        source,
        gltf => {
          gltf.scene.scale.set(25,25,25);
          gltf.scene.rotation.x = 180 * Math.PI/180;
          scene.add(gltf.scene);
        }
      );
    }

    webGLOverlayView.onDraw = ({gl, transformer}) => {
      const latLngAltitudeLiteral = {
        lat: mapOptions.center.lat,
        lng: mapOptions.center.lng,
        altitude: 100
      }

      const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
      camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);

      webGLOverlayView.requestRedraw();
      renderer.render(scene, camera);
      renderer.resetState();
    }

نتعرّف في ما يلي على الصور المتحركة للكاميرا.

8- إضافة تأثيرات حركية إلى الكاميرا

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

  1. انتظِر حتى يتم تحميل النموذج.

    لإنشاء تجربة سلسة للمستخدم، ستكون بحاجة إلى الانتظار لبدء تحريك الكاميرا حتى يتم تحميل نموذج gLTF. للقيام بذلك، أضِف معالج أحداث onLoad للقائم بالتحميل إلى الخطّاف onContextRestored:
    loader.manager.onLoad = () => {}
    
  2. أنشئ حلقة رسوم متحركة.

    هناك أكثر من طريقة واحدة لإنشاء حلقة صور متحركة، مثل استخدام setInterval أو requestAnimationFrame. في هذه الحالة، ستستخدم الوظيفة setAnimationLoop لعارض Three.js، والتي استدعاء تلقائيًا أي رمز تشير إليه في معاودة الاتصال في كل مرة يعرض فيها Three.js إطارًا جديدًا. لإنشاء حلقة الصور المتحركة، أضِف ما يلي إلى معالج أحداث onLoad في الخطوة السابقة:
    renderer.setAnimationLoop(() => {});
    
  3. اضبط موضع الكاميرا في حلقة الصور المتحركة.

    بعد ذلك، اتصل بالرقم moveCamera لتعديل الخريطة. في هذا المثال، يتم استخدام الخصائص من الكائن mapOptions الذي تم استخدامه لتحميل الخريطة لتحديد موضع الكاميرا:
    map.moveCamera({
      "tilt": mapOptions.tilt,
      "heading": mapOptions.heading,
      "zoom": mapOptions.zoom
    });
    
  4. عدِّل الكاميرا لكل إطار.

    الخطوة الأخيرة عليك تعديل العنصر mapOptions في نهاية كل إطار لضبط موضع الكاميرا للإطار التالي. في هذا الرمز، يتم استخدام عبارة if لزيادة الإمالة حتى تصل إلى الحد الأقصى لقيمة الإمالة 67.5، ثم يتم تغيير العنوان قليلاً لكل إطار حتى تكمل الكاميرا دوران كامل بزاوية 360 درجة. بعد اكتمال الصورة المتحركة المطلوبة، يتم تمرير null إلى setAnimationLoop لإلغاء الصورة المتحركة حتى لا يتم تشغيلها إلى الأبد.
    if (mapOptions.tilt < 67.5) {
      mapOptions.tilt += 0.5
    } else if (mapOptions.heading <= 360) {
      mapOptions.heading += 0.2;
    } else {
      renderer.setAnimationLoop(null)
    }
    

من المفترض أن يبدو الخطاف في onContextRestored الآن على النحو التالي:

    webGLOverlayView.onContextRestored = ({gl}) => {
      renderer = new THREE.WebGLRenderer({
        canvas: gl.canvas,
        context: gl,
        ...gl.getContextAttributes(),
      });

      renderer.autoClear = false;

      loader.manager.onLoad = () => {
        renderer.setAnimationLoop(() => {
           map.moveCamera({
            "tilt": mapOptions.tilt,
            "heading": mapOptions.heading,
            "zoom": mapOptions.zoom
          });

          if (mapOptions.tilt < 67.5) {
            mapOptions.tilt += 0.5
          } else if (mapOptions.heading <= 360) {
            mapOptions.heading += 0.2;
          } else {
            renderer.setAnimationLoop(null)
          }
        });
      }
    }

9- تهانينا

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

دبوس ثلاثي الأبعاد النهائي

ما تعلّمته

لقد تعلمت في هذا الدرس التطبيقي مجموعة من العناصر، وإليك أبرزها:

  • تنفيذ ميزة WebGLOverlayView وحلقاتها الحيوية.
  • دمج Three.js في الخريطة.
  • أساسيات إنشاء مشهد Three.js، بما في ذلك الكاميرات والإضاءة.
  • تحميل النماذج الثلاثية الأبعاد ومعالجتها باستخدام Three.js.
  • التحكم في الكاميرا وتحريكها باستخدام الخريطة باستخدام moveCamera.

ما الخطوات التالية؟

موضوع WebGL ورسومات الكمبيوتر بشكل عام موضوع معقد، وبالتالي يظل هناك العديد من الدروس الواجب تعلُّمها. في ما يلي بعض المراجع لمساعدتك في عملية الترقية: