Карта галактики на 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

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

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


    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 11
    • +1
      «в центре какую-то дырку» можно было обыграть как перемычку. Расскажите пожалуйста подробнее о самой игре и почему выбрали именно WebGL?
      • +1
        Ну хотелось браузерную игру, но не текстовую, а хоть с какой-то графикой. Выбор был между HTML 5 Canvas и WebGL. К сожалению производительности канвы мне не хватает, остается WebGL.
        А по самой игре пока ничего особенного не сказать. Был не очень удачный старт, так как было много багов, падал сервер постоянно, да и геймплея было мало. Сейчас работаю над обновлением и хочу к НГ запустить. А по смыслу кто-то дал хорошее определение «брутальные Космические Рейнджеры». Не знаю особо что рассказать.
      • 0
        Круто! Хотелось бы подробностей про саму игру. Может, у вас есть блог? И кстати, где записываться в тестеры?
        • 0
          Нет, блога у меня нет :) Опять же — игра в разработке, тот прототип что сейчас опубликован, не более чем прототип, и я совсем не хочу сюда давать ссылок, слишком он тормозной, только впечатление испортит.
          Как буду начинать второй старт(первый как я уже говорил был провальный), я думаю напишу ещё статью, где заодно попытаюсь разобрать свои ошибки, так как до этой разработки реального опыта в управлении проектами не было, только теоретический. И я допустил кучу ошибок.
          • 0
            Вы не ответили на последний вопрос.: Р
            • 0
              Не заметил) Как запуск приблизится напишу пост, где расскажу подробнее об игре и где и как тестить, так что кнопочка «Подписаться» поможет. Сейчас на игру лучше не смотреть, только впечатление портить.
        • 0
          Побуду немножно КО: сделать бы сетку масштабируемой (при зуме появляется более мелкая), а то в центральных секторах очень уж много звёзд.

          И звёздные системы подразумеваются все одинковые, или каждая звезда генерируется уникальной?
          • 0
            И звёздные системы подразумеваются все одинковые, или каждая звезда генерируется уникальной?

            Вообще здесь просто перечисляются точки-звезды как координаты + название, список передается просто параметром. Детализация об объектах звездной системы(планеты, луны, и т.п.) в отдельном экране.
          • 0
            Для космической игры буквенно-цифровые названия звездных систем несколько скучноваты. Очень интересный подход использовался при генерации названий планет в старой доброй игре Elite. Там был набор слогов: AL, LE, XE, GE, ZA, CE, BI, SO, US, ES, AR, MA, IN, DI, RE, A, ER, AT, EN, BE, RA, LA, VE, TI, ED, OR, QU, AN, TE, IS, RI, ON. Генерировалось несколько псевдослучайных чисел с заданным seed, и на их основе из некоторого количества слогов (от 1 до 4-5) генерировалось уникальное название планеты. Получались очень интересные и звучные названия. Например: Leanis, Zaisrage, Atri, Ceonria, Vele, Edorce, Rerave, Riaren, Uservete, Zazaus, Usqusois, Maquen…

            Можно расширить набор слогов, и тогда получатся названия вроде: Aferjefa, Ravaga, Kezu, Norode, Isid, Hiugarda, Zuviaz, Enzoko, Tiri, Fibaad…
            • 0
              Да, я знаю. У меня в игре есть условно две части пространства — изначальные 5-15 систем, которые считаются исследованными, и у них есть имена, таки как выше как раз таки. А вторая, большая часть пространства(98-99% я бы сказал) не исследовано, и у них как раз таки имена буквенно-цифирные. Примерно та же схема в EVE.
              • +1
                I hate you, RAXXLA.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.