Карта галактики на Three.js/WebGL

от автора

Доброго времени дня или ночи. В свободное время я занимаюсь разработкой игры на космическую тематику на Three.js/WebGL и задумал написать небольшую серию статей по некоторым компонентам игры, в этой статье мы поговорим о карте галактики. Рассказ пойдет уже привычным мне способом — по шагам.

Я не буду приводить код инициализации и подробности самого Three.js, в сети полно информации по этому поводу.
И наш первый шаг…

Шаг 1 — Черный-черный фон

Для начала нам надо сделать подложку. Делается это элементарно в одну строчку:

renderer.setClearColor(0x000000); 

Это будет выглядеть так:

Очевидно это Прямоугольник Малевича и на этом мы первый шаг заканчиваем.
Ведь правда просто?

Шаг 2 — And the Sky Full of Stars

Прямоугольник Малевича — это просто замечательно, но надо всё таки добавить звезды.
Так как мы делаем карту галактики, нам нужна спираль похожая на галактику. Я выбрал логарифмическую спираль.
Сначала записываем все нужные нам переменные

//переменные для построения логарифмической спирали var countStars = 20000; var a = 1.1; мar b = 0.17; var windings = 3.7; var tMax = 2.0 * Math.PI * windings;         var drift = 0.3 

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

//Строим логарифмическую спираль for (var i = 0; i < countStars; i++) {     //формула + рандомное смещение точек     var t = tMax * Math.random();     var x = a * Math.exp(b * t) * Math.cos(t);     x = x + (drift*x*Math.random()) - (drift*x*Math.random());     var y = a * Math.exp(b * t) * Math.sin(t);     y = y + (drift*y*Math.random()) - (drift*y*Math.random())     //Зеркально равномерно распределяем точки     if (Math.random() > 0.5) {         list.push({x:vec.x, y:vec.y});     }     else { //Отражение спирали         list.push({x:-vec.x, y:-vec.y});     } } 

Так как количество точек очень большое, их мы оформляем системой частиц, лагов будет куда меньше:

//геометрия var geometry = new THREE.Geometry(); //Материал системы частиц var material = new THREE.ParticleSystemMaterial({       color: 0xeeeeee,       size: 3 }); //Система частиц var particleSystem = new THREE.ParticleSystem(       geometry,         material ); //Добавляем звезды for (var i = 0; i < list.length; i++) {     addStar(list[i].x, list[i].y);   } scene.add(particleSystem); 

Функция addStar на данном этапе:

var addStar = function(x, y) {     var v = new THREE.Vector3();     v.x = x * 10;     v.y = y * 10;      geometry.vertices.push(v);   } 

И у нас получилось…

Что-то в центре какая-то дырка, давайте поглядим поближе:

Отвратительно, но исправить не сложно, добавим два цикла.
Первым циклом генерим кольцо из точек:

for (var i = 0; i < 4000; i++) {     var vec = {x:Math.sRandom(0.8, 1.7),y:0};     var angle = Math.sRandom(0, Math.PI*2.5);     vec = VectorRot(vec, angle);      list.push({x:vec.x, y:vec.y}); } 

Вторым циклом генерим круг из точек:

for (var i = 0; i < 4000; i++) {     var vec = {x:Math.sRandom(0.001, 0.8),y:0};     var angle = Math.sRandom(0, Math.PI*2.5);     vec = VectorRot(vec, angle);     list.push({x:vec.x, y:vec.y}); } 

Отлично, у нас уже есть что-то похожее на галактику. Но нам нужны имена нашим звездам. У тебя %username% есть имя, а у звезды нет. Разве справедливо?

Шаг 3 — Звезда по имени %starname%

Ну давайте делать последовательно.
Нам нужно сделать функцию для генерирования названия. Нужен глобальный список звезд(координаты + название). Нужно модифицировать добавление звезд и включать туда генерирование название. Это по самому наличию названий. Также нужно функция для вывода названия звезды при событие mouseover. Проблема в том, что так как это система частиц просто так событие не повесить, значит нужно что-то придумать другое. Вариантов много, но я сделал следующее: нашел JS реализацию KDTree, и загнал в дерево все точки которые у нас есть(т.е. тот самый глобальный список), и написал в обработчике события mousemove следующее:

//класический способ перевода координат мыши в мировые координаты var projector = new THREE.Projector(); var vector = new THREE.Vector3( ( e.pageX / window.innerWidth ) * 2 - 1, - ( e.pageY / window.innerHeight ) * 2 + 1, 0.5 );  var pos = projector.unprojectVector( vector, e.data.self.camera ); //дальше пересоздаем отдельную сцену для названия e.data.self.sceneNames = new THREE.Scene(); //Вытаскиваем из KDTree ближайшии звезды от позиции мыши var items = e.data.self.tree.nearest({x:pos.x,y:pos.y}, 1, 100); //Далее создаем собственно label for (var i = 0; i < items.length; i++) {     e.data.self.sceneNames.add(e.data.self.labelBasic(items[i][0].name,vector.x, vector.y, 60, "#f00")); } 

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

Шаг 4 — Вносим порядок в хаос

Итак, у нас уже есть галактика, названия звезд, мы можем их увидеть, но раз у нас карта, то нам нужно разбиение пространства. Если я скажу: «слухай, лети ка ты в систему TX-82 и купи мне кефира», то будет непонятно куда лететь, ибо а) не факт что система с названием TX-82 единственная, б) как найти систему среди over 20k звезд? в) не факт что кефир уже завезли.
Сделаем такое разбиение: есть квадранты, есть сектора. Вся галактика делится на 4*4=16 квадрантов, по 4 с каждой стороны. Каждый квадрант, в свою очередь, делиться на 4 сектора. Т.е. мы можем адресовать систему как квадрант #qX-qY — сектор (sx-sy) — система %starname%.
Делаем мы это банальными линиями, код опять же не привожу, он большой и не интересный — просто расчет координат начала и конца каждой линии. Кому интересно — добро пожаловать на гитхаб.
Результат, как вы могли догадаться, в шапке статьи находится. Но я приведу ещё одну картинку:

Только нужно ещё добавить обозначения — текст, вроде (1-1),(3-3),(2-3). Сетка есть, а обозначений нет. Добавляем.

Шаг 5 — Чип и Дейл

Спасем от непонимания что это за числа выше на картинке. Или хотя бы попытаемся. Да. Две строчки HTML и CSS:

<span id="quad" style="position:absolute;left:100px;top:10px;color:white;font-family:Arial;font-size:19px">#x-y Quadrant</span> <span id="sector" style="position:absolute;left:100px;top:30px;color:#555;font-family:Arial;font-size:19px">(sx-sy) Sector</span> 

Шаг 6 — Где я?

И последнее что нам осталось — указать наше положении в галактики. Стукнул кирпич по голове, забыл где находишся. А нам ведь нужно сказать куда привести кефир, что же нам делать? Открываем карту и спасибо технологиям:

Да, код такой:

//Добавить маркер var addMarker = function(x, y) {     //геометрия     var g = new THREE.Geometry();     //Материал системы частиц     var m = new THREE.ParticleBasicMaterial({           color: 0x550000,           size: 35     });     for (var i = 0; i < 100; i++) {         g.vertices.push({x:x,y:y});     };          //Система частиц     var p = new THREE.ParticleSystem(           g,           m     );      this.sceneLabel.add(this.labelBasic(">>                ", x , y , 70, "#f00"));     this.sceneLabel.add(this.labelBasic("                <<", x , y , 70, "#f00"));      this.sceneLabel.add(this.labelBasic(this.points[this.here].name, x , y , 60, "#f00"));      this.sceneLabel.add(p); } 

Заключение

Итак, мы сделали карту. Я не привел в статье многие вещи, например смещение карты по зажатой клавише, зум, не сильно детализировал работу с Three.js, на мой взгляд это вторично и не так интересно.
Гитхаб: github.com/MagistrAVSH/galmap
Демо работает в последних FF, Chrome, Opera. В IE11 работать будет плохо, вы не увидите надписей вообще, он криво поддерживает WebGL. magistravsh.github.io/galmap/

На будущее есть идеи написать статьи про карту звездной системы, про генератор туманностей, генераторы различных объекты, и вообще на тему фантастического космоса 🙂 Если вам это будет интересно — пишите.

Напоследок под спойлером ещё парочка скриншотов.

Скриншоты

ссылка на оригинал статьи http://habrahabr.ru/post/201790/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *