Создание программы просмотра созвездий для браузера

Около месяца назад я объявил о своем уходе из Songkick и готовности работать на фрилансе. К счастью для меня, у нескольких прошлых клиентов этой осенью выходят новые записи, в том числе Foo Fighters с песней Concrete and Gold. 🤘🏻 Потратив неделю на создание простого приложения, посвященного затмениям, мой мозг вертелся на небесной математике. Как выяснилось, Грол тоже. Дэйв готовил обработку для нового музыкального видео на The Sky is a Neighborhood, в котором группа играет на крыше хижины перед звездным небом. Помимо написания лечения, у Дэйва была интерактивная идея. По его собственным словам:

Мы должны сделать видео выступления, которое связано с этим
Итак, это видео, на котором мы выступаем
Но когда вы наводите его на небо, оно показывает фактические созвездия позади нас.
Так что это работает просто как эти приложения
Но мы играем на переднем плане

Похоже, Дэйв хочет клонировать зрителя созвездия, но на передний план поставил выступление группы. Я копаю это. Пытаясь сделать этот опыт максимально доступным, я решил создать его как приложение для мобильного браузера. Если там, где вы живете, достаточно темно, попробуйте сегодня вечером и продолжайте читать, чтобы узнать, как нам это удалось.

Небесная сфера

Помните, когда люди думали, что Земля плоская? Нет, не сейчас ... 😓 Я говорю о древних временах. В те дни была еще одна замечательная идея: звезды были прикреплены к или дырам на очень большой сфере на большом расстоянии. Естественно, эта сфера была преградой в небеса ... Все еще преследуете? Хотя эта мысль была знамением времени, идея изобразить звезды, галактики и даже наше солнце на небесной сфере - это отличная идея.

Мы можем представить себе небесную сферу как воображаемую сферу огромного радиуса, в центре которой находится Земля. И полюса, и экватор небесной сферы совпадают с полюсами Земли. Мы наносим на карту небесные объекты, используя систему координат, аналогичную земной.

Давайте построим один.

Строительство небесной сферы

Поскольку я знал, что собираюсь создать это приложение с использованием three.js, мне нужно было придумать способ изобразить созвездия неба на сфере. К счастью для меня, Олаф Фрон создал библиотеку D3, метко названную d3-celestial. Спасибо, Олаф! Хотя D3 не был тем, что мне было нужно с точки зрения визуализации, работа Олафа по переводу созвездий в формат GeoJSON обеспечила прочную основу для построения объектов на основе системы координат. В дополнение к линиям созвездий Олаф создал файлы данных для границ и местоположений.

Следуя совету Майка Бостока из этой статьи GeoJSON в Three.js, я преобразовал GeoJSON в Topojson, используя topojson для удаления визуальных артефактов с меридиана. TL; DR Я удалил строки, проходящие через созвездия.

Начните структуру three.js обычно с инициализации сцены, камеры и средства визуализации. Затем вы можете использовать jQuery или что-то вроде Preload.JS для импорта данных JSON.

$.getJSON("constellations.json", function(data) {
  // Universe
});

Из этого вызова импорта JSON мы вызовем функцию wireframe, передав функции topojson topojson.mesh данные, которые мы импортировали. Эта функция создаст сетку геометрии TopoJSON и преобразует ее в линии MultiLineString. Мы также инициализируем базовый материал, чтобы установить эстетику линий созвездия.

celestial = wireframe(
  topojson.mesh(astrims, astrims.objects.collection),
  new THREE.LineBasicMaterial({ color: 0xFFFFFF })
)
scene.add(celestial);

Давайте подробнее рассмотрим, что происходит в этой каркасной функции. Учитывая нашу недавно созданную сетку TopoJSON, мы переберем каждую пару координат для каждого из наших созвездий и преобразуем эти координаты в THREE.Vector3, чтобы мы могли построить их в трехмерном пространстве в виде линий.

function wireframe(multilinestring, material) {
  var geometry = new THREE.Geometry;
  
  multilinestring.coordinates.forEach(function(line) {
    d3.pairs(line.map(vertex), function(a, b) {
      geometry.vertices.push(a, b);
    });
  });
  
  return new THREE.LineSegments(geometry, material);
}

На этом этапе у нас должно быть сферическое отображение данных нашего созвездия. Поскольку в любой момент времени мы можем видеть только северное полушарие нашей небесной сферы, имеет смысл разместить камеру в центре нашей сферы лицом вверх. Это дает дополнительное преимущество в том, что это отличный способ проверить нашу визуализацию с чем-то вроде генератора изображений карты звездного неба AstroView. Это окажется решающим позже, когда мы начнем вращать элементы.

camera.position.y = -200;
camera.position.z = 180 * Math.PI / 180;
camera.rotation.x = 90 * Math.PI / 180;

По без веской причины, кроме того, что это выглядит круто, попробуйте подключить свою сцену three.js к некоторым элементам управления трекболом. Теперь вы можете исследовать небесную сферу с помощью жестов мыши.

controls = new THREE.TrackballControls(camera);

Не забудьте удалить это, когда закончите возиться.

Таким образом, мы смогли изобразить созвездия на сфере и разместить камеру, направленную вверх, в сторону северного небесного полушария. Это служит отличной базой для всех наших фанатов на северном полюсе, но нам нужно будет вращать небесную сферу для всех остальных.

Вращайте небесную сферу

Небесную сферу необходимо повернуть, чтобы она соответствовала ночному небу нашего текущего пользователя. Для этого нам понадобятся две переменные: их координаты и текущее время. Чтобы получить координаты текущего пользователя, мы воспользуемся API HTML5 Геолокация для определения местоположения пользователя.

if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(function(position) {
    // position.coords.latitude
    // position.coords.longitude
  });
} else {
  // Geolocation is not supported by this browser.
}

Затем мы воспользуемся широтой, чтобы повернуть небесную сферу вокруг оси x.

celestial.rotation.x = (latitude - 90) * Math.PI / 180;

Нам также понадобится процент пройденного дня, который можно получить с помощью небольшой Date() логики. Используя процент дня вместе с требуемым смещением, мы можем вращать небесную сферу вокруг оси Y и, в свою очередь, ориентировать небо так, чтобы оно соответствовало небу пользователя.

var d       = new Date();
var percent = (d.getHours() / 24 + d.getMinutes() / (60 * 24));
var offset  = 45;
var angle   = (percent * 360) + offset;
var rotate  = - angle * Math.PI / 180;
celestial.rotation.y = - angle * Math.PI / 180;

Как и в предыдущем тестировании, вам нужно направить камеру вверх и проверить свое местоположение с помощью Astroviewer. Если что-то не совпадает, присмотритесь к математике ротации. Мне нужна была белая доска, чтобы все работало правильно.

Звезды

Я хочу написать совершенно отдельную статью о самих звездах, потому что это оказалось для меня огромным опытом обучения. Я расскажу о таких темах, как индекс цвета B-V и шейдеры three.js. Следите за обновлениями части 2.

Ориентация устройства

Мы можем получить доступ к информации о движении, предоставляемой гироскопом и акселерометром устройства, с помощью событий HTML5 DeviceOrientation. Однако сопоставление этих событий с камерой three.js может быть немного дезориентирующим, но, к счастью для нас, three.js предоставляет библиотеку, которая делает именно это. Как и в случае с THREE.TrackballControls ранее, мы просто хотим подключить эти новые элементы управления к нашей камере.

controls = new THREE.DeviceOrientationControls(camera);

Теперь, когда вы запустите приложение на поддерживаемом устройстве, вы заметите, что при перемещении устройства камера, которую мы разместили в пределах небесной сферы, также позиционирует себя. Довольно круто, но есть еще одна проблема. Нам нужно выяснить, где север.

Лицом на север

Когда устройство пользователя впервые вызывает функцию ориентации устройства, оно думает, что север - это независимо от того, куда смотрит пользователь. Как вы понимаете, незнание, где находится север, полностью отбрасывает весь этот опыт.

THREE.DeviceOrientationControls поставляется с удобной alphaOffsetAngle функцией для исправления этой ситуации, но мы не можем просто подключить элементы управления к компасу нашего устройства. Неудивительно, что когда вы ориентируете свое устройство в нескольких случайных положениях, компас просто не успевает за ним и легко дезориентируется. От этого у камеры кружится голова. Поскольку на самом деле нам нужно только убедиться, что пользователь смотрит на север один раз, я решил сделать это частью адаптации, а не пытаться форсировать сложное техническое решение. После того, как пользователь поделился своей геолокацией, он должен использовать простой компас, чтобы повернуться к северу, прежде чем он сможет перейти к астрономическому навигатору.

window.addEventListener("deviceorientation", function(event) {
  // event.webkitCompassHeading
});

Пока пользователь смотрит на север при включенном DeviceOrientationControls, все будет работать нормально. Поскольку решение, о котором я упоминал выше, работает только на устройствах iOS, я фактически удалил его из окончательной сборки и просто попросил нашего пользователя повернуться лицом к северу. Достаточно близко для рок-н-ролла. 😅

Прозрачное видео

Вы могли подумать, что вся эта небесная математика будет самой сложной частью проекта, верно? Что ж, друг мой, ты ошибаешься. Попытка автовоспроизвести прозрачное встроенное видео была настоящим чудом, и я хотел бы увидеть решение этой проблемы Аристотелем. К счастью, эта статья Грега Дорсейнвилля представила рабочее решение, которое я применил на своем опыте.

Сначала вы создаете одно сложенное видео, которое содержит видео в формате RGB и Alpha. Затем вы добавляете это видео в качестве источника к скрытому тегу <video>. Наконец, во время воспроизведения видео нарисуйте данные изображения на двух элементах холста: буфере и выводе. Затем Buffer получит информацию об альфа-канале и повторно применит прозрачность к материалу RGB, в результате чего на выходной холст будет нарисовано прозрачное видео. Это холст, который вы показываете пользователям.

output.drawImage(video, 0, 0);
buffer.drawImage(video, 0, 0);
var image = buffer.getImageData(0, 0, 414, 233);
imageData = image.data;
alphaData = buffer.getImageData(0, 233, 414, 233).data;
for (var i = 3; i < imageData.length; i += 4) {
  imageData[i] = alphaData[i - 1];
}
output.putImageData(image, 0, 0, 0, 0, 414, 233);

Небольшой совет: заранее загрузите видео, если оно содержит звук, и сначала воспроизводите его без звука. Это предотвратит попытку вашего браузера воспроизвести его в полноэкранном режиме. Эти правила и положения об автовоспроизведении мультимедиа продолжают развиваться, поэтому будьте начеку для изменений в стандартизации.

Как всегда, спасибо за внимание. Запустите приложение сегодня вечером и посмотрите, сможете ли вы заметить созвездия. Если вам нравится научная фантастика 70-х, смешанная с атмосферой Stranger Things, обязательно посмотрите новое видео на The Sky is a Neighborhood. "Concrete and Gold" выйдет на лейбле RCA Records 12 сентября. Особая благодарность Foo Fighters и Silva Artist Management за помощь в оплате аренды.