كسب 100,000 نجمة

مرحبًا اسمي "مايكل تشانغ" وأعمل مع فريق "فنون البيانات" في Google. لقد أكملنا مؤخرًا 100,000 نجم، وهي تجربة Chrome تعرض النجوم القريبة. تم إنشاء المشروع باستخدام THREE.js وCSS3D. في هذه الدراسة، سأوضّح عملية الاكتشاف وأشارك بعض أساليب البرمجة وأقدّم بعض الأفكار للتحسين في المستقبل.

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

100,000 Stars، تجربة في Chrome من فريق "الفنون المستندة إلى البيانات"
يستخدم مشروع 100,000 Stars مكتبة THREE.js لعرض النجوم القريبة في درب التبانة

استكشاف المساحة

بعد وقت قصير من الانتهاء من Small Arms Globe، كنت أُجري تجربة على عرض تجريبي لجزيئات THREE.js مع عمق المجال. لاحظتُ أنّه يمكنني تغيير "المقياس" المتفَسَّر للمشهد من خلال تعديل مقدار التأثير المُطبَّق. عندما كان تأثير عمق المجال شديدًا، أصبحت الأجسام البعيدة مموّهة جدًا، تمامًا مثل الطريقة التي تعمل بها تقنية التصوير بالميل والتكبير/التصغير لإعطاء الشخص الوهم بأنّه ينظر إلى مشهد مجهري. في المقابل، عند إيقاف التأثير، يبدو أنّك تنظر إلى الفضاء الخارجي.

بدأت البحث عن بيانات يمكنني استخدامها لإدخال مواقع الجسيمات، ما قادني إلى قاعدة بيانات HYG على الموقع الإلكتروني astronexus.com، وهي عبارة عن تجميع لمصادر البيانات الثلاثة (Hipparcos وYale Bright Star Catalog وGliese/Jahreiss Catalog) مع إحداثيات xyz الكرتيزية المحسوبة مسبقًا. لنبدأ.

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

استغرقت العملية ساعة تقريبًا لإنشاء نموذج يعرض بيانات النجوم في مساحة ثلاثية الأبعاد. تتضمّن مجموعة البيانات 119,617 نجمًا بالضبط، لذا لا يشكّل تمثيل كل نجم بجسيم مشكلة لوحدة معالجة الرسومات الحديثة. هناك أيضًا 87 نجمة تم تحديدها بشكل فردي، لذلك أنشأت تراكب علامة CSS باستخدام التقنية نفسها التي وصفتها في "كرة الأسلحة الصغيرة".

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

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

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

إنشاء مجرّة

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

نموذج أوّلي مبكر لهاتف Galaxy
نموذج أوّلي مبكر لنظام الجسيمات في مجرّة درب التبانة

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

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

معرفة حجم المجرة
كل وحدة GL هي سنة ضوئية. في هذه الحالة، يبلغ قطر الكرة 110,000 سنة ضوئية، وتضم نظام الجسيمات.

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

ومن القواعد الأخرى التي اتّبعتها هي تدوير المشهد بالكامل بدلاً من تحريك الكاميرا، وهو ما سبق أن فعلته في بعض المشاريع الأخرى. ومن المزايا أنّه يتم وضع كل شيء على "أداة تدوير" حتى يؤدي سحب الماوس إلى اليسار واليمين إلى تدوير الجسم المعني، ولكن تكبير الصورة ليس سوى مسألة تغيير camera.position.z.

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

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

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

النظام الشمسي
الشمس تدور حولها الكواكب وكرة تمثّل حزام كايبر

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

تم استخدام أسلوب مشابه لتاج الشمس، باستثناء أنّه سيكون بطاقة رسوم متحركة مسطّحة تواجه الكاميرا دائمًا باستخدام https://github.com/mrdoob/three.js/blob/master/src/extras/core/Gyroscope.js.

عرض Sol
إصدار مبكر من الشمس

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

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

نموذج النجوم
تمّ تعميم الرمز البرمجي لعرض الشمس لاحقًا لعرض النجوم الأخرى.

لقد استخدمتُ بعض الحيل للتخفيف من ظاهرة z-fighting. سمة Material.polygonoffset في THREE هي سمة تسمح بعرض المضلّعات في موقع مختلف (حسب فهمي). وقد تم استخدام هذا الإجراء لفرض عرض سطح الهالة دائمًا فوق سطح الشمس. أسفل ذلك، تم عرض "هالة" للشمس لإعطاء أشعة ضوء حادة تتحرك بعيدًا عن الكرة.

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

إنشاء تأثير "عدسة الكاميرا"

مع زيادة القدرات تزداد المسؤولية.
مع زيادة القدرات تزداد المسؤولية.

أعتقد أنّه يمكنني استخدام تأثير "وميض العدسة" بشكل مفرط في الرسومات المرئية للأماكن. يخدم تأثير THREE.LensFlare هذه الأغراض، وكل ما كان عليّ فعله هو إضافة بعض الأشكال السداسية المجسمة وقليل من لمسات JJ Abrams. يوضّح المقتطف أدناه كيفية إنشائها في المشهد.

// This function returns a lesnflare THREE object to be .add()ed to the scene graph
function addLensFlare(x,y,z, size, overrideImage){
var flareColor = new THREE.Color( 0xffffff );

lensFlare = new THREE.LensFlare( overrideImage, 700, 0.0, THREE.AdditiveBlending, flareColor );

// we're going to be using multiple sub-lens-flare artifacts, each with a different size
lensFlare.add( textureFlare1, 4096, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );
lensFlare.add( textureFlare2, 512, 0.0, THREE.AdditiveBlending );

// and run each through a function below
lensFlare.customUpdateCallback = lensFlareUpdateCallback;

lensFlare.position = new THREE.Vector3(x,y,z);
lensFlare.size = size ? size : 16000 ;
return lensFlare;
}

// this function will operate over each lensflare artifact, moving them around the screen
function lensFlareUpdateCallback( object ) {
var f, fl = this.lensFlares.length;
var flare;
var vecX = -this.positionScreen.x _ 2;
var vecY = -this.positionScreen.y _ 2;
var size = object.size ? object.size : 16000;

var camDistance = camera.position.length();

for( f = 0; f < fl; f ++ ) {
flare = this.lensFlares[ f ];

flare.x = this.positionScreen.x + vecX * flare.distance;
flare.y = this.positionScreen.y + vecY * flare.distance;

flare.scale = size / camDistance;
flare.rotation = 0;

}
}

طريقة سهلة لتحريك النسيج

مستوحاة من Homeworld
مستوى إحداثي للمساعدة في التوجيه المكاني في الفضاء

بالنسبة إلى "مستوى التوجيه المكاني"، تم إنشاء عنصر THREE.CylinderGeometry() ضخم وتم وضعه في منتصف الشمس. لإنشاء "موجة من الضوء" تنتشر للخارج، عدّلت إزاحة نسيج الموجة بمرور الوقت على النحو التالي:

mesh.material.map.needsUpdate = true;
mesh.material.map.onUpdate = function(){
this.offset.y -= 0.001;
this.needsUpdate = true;
}

map هو نسيج ينتمي إلى المادة، والذي يحصل على وظيفة onUpdate يمكنك استبدالها. يؤدي ضبط الإزاحة إلى "تمرير" النسيج على طول هذا المحور، وسيؤدي إرسال الرسائل غير المرغوب فيها مع ضبط needsUpdate = true إلى فرض تكرار هذا السلوك.

استخدام منحنيات الألوان

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

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

var shaderMaterial = new THREE.ShaderMaterial( {
uniforms: datastarUniforms,
attributes: datastarAttributes,
/_ ... etc _/
});
var datastarAttributes = {
size: { type: 'f', value: [] },
colorIndex: { type: 'f', value: [] },
};

سيؤدي ملء صفيف colorIndex إلى منح كل جسيم لونه الفريد في برنامج تشويش الصورة. عادةً ما يتم تمرير vec3 للون، ولكن في هذه الحالة، أُدخِل قيمة float للبحث عن مخطط الألوان النهائي.

تدرج الألوان
مخطط ألوان يُستخدَم للاطّلاع على اللون المرئي من مؤشر لون النجم.

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

// make a blank canvas, sized to the image, in this case gradientImage is a dom image element
gradientCanvas = document.createElement('canvas');
gradientCanvas.width = gradientImage.width;
gradientCanvas.height = gradientImage.height;

// draw the image
gradientCanvas.getContext('2d').drawImage( gradientImage, 0, 0, gradientImage.width, gradientImage.height );

// a function to grab the pixel color based on a normalized percentage value
gradientCanvas.getColor = function( percentage ){
return this.getContext('2d').getImageData(percentage \* gradientImage.width,0, 1, 1).data;
}

وتُستخدَم هذه الطريقة نفسها بعد ذلك لتلوين النجوم الفردية في عرض نموذج النجوم.

عيوني
تُستخدَم التقنية نفسها للبحث عن اللون حسب الفئة الطيفية للنجم.

حلّ المشاكل المتعلّقة بالظلال

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

// list of shaders we'll load
var shaderList = ['shaders/starsurface', 'shaders/starhalo', 'shaders/starflare', 'shaders/galacticstars', /*...etc...*/];

// a small util to pre-fetch all shaders and put them in a data structure (replacing the list above)
function loadShaders( list, callback ){
var shaders = {};

var expectedFiles = list.length \* 2;
var loadedFiles = 0;

function makeCallback( name, type ){
return function(data){
if( shaders[name] === undefined ){
shaders[name] = {};
}

    shaders[name][type] = data;

    //  check if done
    loadedFiles++;
    if( loadedFiles == expectedFiles ){
    callback( shaders );
    }

};

}

for( var i=0; i<list.length; i++ ){
var vertexShaderFile = list[i] + '.vsh';
var fragmentShaderFile = list[i] + '.fsh';

//  find the filename, use it as the identifier
var splitted = list[i].split('/');
var shaderName = splitted[splitted.length-1];
$(document).load( vertexShaderFile, makeCallback(shaderName, 'vertex') );
$(document).load( fragmentShaderFile,  makeCallback(shaderName, 'fragment') );

}
}

تأخذ الدالة loadShaders() قائمة بأسماء ملفات shaders (تتوقع ‎ .fsh لملفات shaders للشرائح و‎ .vsh لملفات shaders للرؤوس)، وتحاول تحميل بياناتها، ثم تستبدل القائمة بكائنات. النتيجة النهائية هي في تصاميم THREE.js التي يمكنك تمرير مواد تشويش إليها على النحو التالي:

var galacticShaderMaterial = new THREE.ShaderMaterial( {
vertexShader: shaderList.galacticstars.vertex,
fragmentShader: shaderList.galacticstars.fragment,
/_..._/
});

كان بإمكاني على الأرجح استخدام require.js، إلا أنّ ذلك كان سيتطلّب إعادة تجميع بعض الرموز البرمجية لهذا الغرض فقط. على الرغم من أنّ هذا الحلّ أسهل بكثير، إلا أنّه يمكن تحسينه، ربما حتى كإضافة THREE.js. إذا كانت لديك اقتراحات أو طرق لإجراء ذلك بشكل أفضل، يُرجى إعلامي بها.

تصنيفات نصية في CSS فوق THREE.js

في مشروعنا الأخير، Small Arms Globe، جرّبتُ عرض تصنيفات نصية فوق مشهد THREE.js. تحسب الطريقة التي كنت أستخدمها موضع النموذج المطلق للمكان الذي أريد ظهور النص فيه، ثمّ تُحدّد موضع الشاشة باستخدام THREE.Projector()، وأخيرًا تستخدِم CSS "top" و "left" لوضع عناصر CSS في الموضع المطلوب.

استخدَمت النُسخ الأولى من هذا المشروع هذه التقنية نفسها، ولكنّني كنت أريد تجربة هذه الطريقة الأخرى التي وصفها لويس كروز.

الفكرة الأساسية: مطابقة عملية تحويل المصفوفة في CSS3D مع الكاميرا والمشهد في THREE، ويمكنك "وضع" عناصر CSS في 3D كما لو كانت فوق مشهد THREE. ومع ذلك، هناك بعض القيود المفروضة على ذلك، على سبيل المثال، لن تتمكّن من وضع نص أسفل عنصر THREE.js. ولا يزال هذا الإجراء أسرع بكثير من محاولة تنفيذ التنسيق باستخدام سمتَي CSS "top" و "left".

التصنيفات النصية
استخدام عمليات التحويل في CSS3D لوضع التصنيفات النصية فوق WebGL

يمكنك العثور على العرض التجريبي (والرمز في عرض المصدر) لهذا الإجراء هنا. ومع ذلك، تبيّن لي أنّ ترتيب المصفوفة قد تغيّر منذ ذلك الحين في THREE.js. الدالة التي عدّلتها:

/_ Fixes the difference between WebGL coordinates to CSS coordinates _/
function toCSSMatrix(threeMat4, b) {
var a = threeMat4, f;
if (b) {
f = [
a.elements[0], -a.elements[1], a.elements[2], a.elements[3],
a.elements[4], -a.elements[5], a.elements[6], a.elements[7],
a.elements[8], -a.elements[9], a.elements[10], a.elements[11],
a.elements[12], -a.elements[13], a.elements[14], a.elements[15]
];
} else {
f = [
a.elements[0], a.elements[1], a.elements[2], a.elements[3],
a.elements[4], a.elements[5], a.elements[6], a.elements[7],
a.elements[8], a.elements[9], a.elements[10], a.elements[11],
a.elements[12], a.elements[13], a.elements[14], a.elements[15]
];
}
for (var e in f) {
f[e] = epsilon(f[e]);
}
return "matrix3d(" + f.join(",") + ")";
}

بما أنّه تم تحويل كل شيء، لم يعُد النص موجهًا للكاميرا. كان الحلّ هو استخدام THREE.Gyroscope() الذي يفرض على Object3D "فقدان" الاتجاه المُكتسَب من المشهد. تُعرف هذه التقنية باسم "عرض الإعلانات على لوحات الإعلانات"، وتعد ميزة "التسارع الدوراني" مثالية لتنفيذ ذلك.

من الجميل أنّه لا يزال بإمكانك استخدام جميع عناصر DOM وCSS العادية، مثل تمرير مؤشر الماوس فوق تصنيف نص ثلاثي الأبعاد وإضاءته باستخدام الظلال المتساقطة.

التصنيفات النصية
جعل تصنيفات النصوص تواجه الكاميرا دائمًا من خلال إرفاقها بـ THREE.Gyroscope().‎

عند التكبير، تبيّن لي أنّ تغيير حجم الخط كان يتسبب في مشاكل في موضع النص. هل يرجع ذلك إلى تباعد الأحرف وملء النص؟ كانت المشكلة الأخرى هي أنّ النص أصبح مجزّأً عند التكبير لأنّ أداة عرض DOM تتعامل مع النص المعروض كشكل رباعي مزوّد بنسيج، وهو أمر يجب أخذه في الاعتبار عند استخدام هذه الطريقة. بعد النظر في الأمر، كان بإمكاني استخدام نص بحجم خط ضخم، وربما يكون هذا أمرًا يمكن استكشافه في المستقبل. في هذا المشروع، استخدمتُ أيضًا تصنيفات نص موضع CSS "top/left" الموضّحة سابقًا للعناصر الصغيرة جدًا التي تصاحب الكواكب في النظام الشمسي.

تشغيل الموسيقى وتشغيلها بشكل متكرّر

إنّ المقطع الموسيقي الذي يتم تشغيله أثناء "الخريطة المجرية" في لعبة Mass Effect من تأليف مؤلفَي الموسيقى في Bioware، وهما سام هوليك وجاك وول، وقد عبّر عن المشاعر التي أردت أن يشعر بها الزائر. أردنا إضافة بعض الموسيقى إلى مشروعنا لأنّنا شعرنا أنّها جزء مهم من الأجواء، وتساعد في خلق الشعور بالرهبة والإعجاب الذي كنا نحاول تحقيقه.

تواصل منتجنا فالديان كلومب مع سام الذي كان لديه مجموعة من المقاطع الموسيقية من Mass Effect التي سمح لنا باستخدامها. عنوان المقطع الصوتي هو "In a Strange Land".

لقد استخدمتُ علامة الصوت لتشغيل الموسيقى، ولكن حتى في Chrome، كانت سمة "حلقة" غير موثوقة، ففي بعض الأحيان لا يمكن تشغيل الموسيقى بشكل متكرّر. في النهاية، تم استخدام هذه الطريقة لفحص نهاية التشغيل والانتقال إلى العلامة الأخرى لتشغيلها. كان من المخيِّب للآمال أنّ هذا الإطار لم يكن يُعاد عرضه بشكلٍ مثالي طوال الوقت، ولكن أعتقد أنّه كان أفضل ما يمكنني فعله.

var musicA = document.getElementById('bgmusicA');
var musicB = document.getElementById('bgmusicB');
musicA.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playB = function(){
musicB.play();
}
// make it wait 15 seconds before playing again
setTimeout( playB, 15000 );
}, false);

musicB.addEventListener('ended', function(){
this.currentTime = 0;
this.pause();
var playA = function(){
musicA.play();
}
// otherwise the music will drive you insane
setTimeout( playA, 15000 );
}, false);

// okay so there's a bit of code redundancy, I admit it
musicA.play();

فرص التحسين

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

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

لا تزال العناصر الكتابية المصحوبة بـ WebGL تشكل تحديًا، وأنا لست متأكّدًا بنسبة% 100 من أنّ ما نفعله هنا هو الطريقة الصحيحة. لا يزال يبدو أنّه تم اختراق حسابك. من المحتمل أن تُستخدَم الإصدارات المستقبلية من THREE، مع محرِّك CSS القادم، لدمج هذين العالمَين بشكل أفضل.

المساهمون

شكرًا لـ "آرون كوبلن" على السماح لي بتنفيذ هذا المشروع. شكرًا لجونو براندل على تصميم واجهة المستخدم الممتاز وتنفيذها ومعالجة النصوص وتنفيذ الجولة. شكرًا لـ Valdean Klump على اختيار اسم للمشروع وكتابة جميع النصوص. شكرًا لصباح أحمد على المساعدة في الحصول على حقوق استخدام مصادر البيانات والصور. "كليم رايت" للتواصل مع الأشخاص المناسبين للنشر "دوغ فريتز" للتميز الفني جورج براون الذي علّمني JavaScript وCSS وبالطبع، لدينا Mr. Doob لـ THREE.js.

المراجع