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

1. قبل البدء

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

Final 3D Pin

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

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

أهداف الدورة التعليمية

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

المتطلبات

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

2. طريقة الإعداد

في خطوة التفعيل أدناه، عليك تفعيل Maps JavaScript API.

إعداد Google Maps Platform

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

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

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

إعداد Node.js

إذا لم يكن لديك 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، الذي يجمع الرمز الذي تكتبه ويشغّله محليًا. يعيد webpack-dev-server أيضًا تحميل تطبيقك تلقائيًا في المتصفّح كلما أجريت تغييرات على الرمز.

إذا أردت الاطّلاع على رمز الحلّ الكامل قيد التشغيل، يمكنك إكمال خطوات الإعداد أعلاه في الدليل /solution.

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

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

لإضافة مفتاح واجهة برمجة التطبيقات، اتّبِع الخطوات التالية في المشروع المبدئي:

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

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

لاستخدام الميزات المستندة إلى WebGL في Maps JavaScript API، يجب توفير رقم تعريف خريطة مع تفعيل الخريطة المتجهة.

إنشاء معرّف خريطة

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

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

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

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

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

لتحميل الخريطة باستخدام رقم تعريف الخريطة، اتّبِع الخطوات التالية:

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

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

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

إذا لم يتم تحميل الخريطة، تأكَّد من أنّك قدّمت مفتاح API صالحًا في 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 خمس نقاط ربط في دورة حياة سياق عرض WebGL للخريطة يمكنك استخدامها. في ما يلي وصف سريع لكل خطاف والحالات التي يجب استخدامه فيها:

  • 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().

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

    لتنفيذ خطافات مراحل النشاط، أضِف ما يلي إلى initWebGLOverlayView():
    webGLOverlayView.onAdd = () => {};
    webGLOverlayView.onContextRestored = ({gl}) => {};
    webGLOverlayView.onDraw = ({gl, transformer}) => {};
    
  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, transformer}) => {}
      webGLOverlayView.setMap(map);
    }

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

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

5- إعداد مشهد three.js

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

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

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

في هذه الخطوة، ستحمّل جميع العناصر التابعة اللازمة لـ Three.js وتعدّ مشهدًا أساسيًا.

  1. تحميل three.js

    ستحتاج إلى عنصرَين تابعَين في هذا الدرس العملي: مكتبة Three.js وGLTF Loader، وهي فئة تتيح لك تحميل عناصر ثلاثية الأبعاد بتنسيق GL Trasmission Format (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. إنشاء مشهد ثلاثي الأبعاد باستخدام three.js

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

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

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

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

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

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

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

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

    لإضافة مصادر الإضاءة إلى المشهد، أضِف ما يلي إلى خطاف 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 المحتوى الثنائي والثلاثي الأبعاد في المتصفح باستخدام واجهة برمجة تطبيقات لوحة الرسم. إذا سبق لك استخدام Canvas API، من المحتمل أنّك على دراية context في لوحة HTML، وهو المكان الذي يتم فيه عرض كل شيء. قد لا تعرف أنّ هذه الواجهة تعرض سياق عرض رسومات OpenGL من خلال واجهة برمجة التطبيقات WebGLRenderingContext في المتصفّح.

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

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

  1. إعداد أداة العرض WebGL

    عند إنشاء مثيل جديد من Three.js WebGLRenderer، يمكنك تزويده بسياق العرض الخاص بـ 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 Overlay View يعتمد على حالة 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 Overlay View وأنشأت مشهد Three.js، ولكن هناك مشكلة واحدة: لا يوجد أي شيء فيه. بعد ذلك، حان الوقت لعرض عنصر ثلاثي الأبعاد في المشهد. لإجراء ذلك، ستستخدم أداة تحميل GLTF التي استوردتها سابقًا.

تتوفّر التصاميم الثلاثية الأبعاد بتنسيقات مختلفة، ولكن بالنسبة إلى Three.js، يُفضّل استخدام تنسيق gLTF بسبب حجمه وأدائه أثناء التشغيل. في هذا الدرس العملي، يتم توفير نموذج لعرضه في المشهد في /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. يتم تحديد مصفوفة العرض على شكل مصفوفة Matrix4 في Three.js، وهي تحدد نقطة في مساحة ثلاثية الأبعاد مع عمليات التحويل، مثل عمليات التدوير والقص والقياس وغيرها.

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

    في الرمز البرمجي أدناه، يطلب الوسيط الثاني من WebGL Overlay View ضبط ارتفاع مشهد 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. حوِّل النموذج.

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

    في حالة هذا النموذج، لم يتم إنشاؤه باستخدام ما نعتبره عادةً "أعلى" الدبوس الذي يواجه المحور y، لذا عليك تحويل العنصر لتوجيهه بالطريقة المطلوبة بالنسبة إلى مساحة العالم من خلال استدعاء 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- تهانينا

إذا سارت الأمور على ما يرام، من المفترض أن تظهر لك الآن خريطة تحتوي على دبوس ثلاثي الأبعاد كبير يبدو على النحو التالي:

Final 3D Pin

ما تعلّمته

لقد تعلّمت الكثير في هذا الدرس العملي، وفي ما يلي أهم النقاط:

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

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

إنّ WebGL ورسومات الكمبيوتر بشكل عام موضوع معقّد، لذا هناك دائمًا الكثير لتعلّمه. في ما يلي بعض المراجع لمساعدتك في عملية الترقية: