Galaxy Map on Three.js / WebGL
Good day or night. In my free time I am developing a space-themed game on Three.js / WebGL and decided to write a small series of articles on some components of the game, in this article we will talk about the galaxy map. The story will go in the usual way for me - in steps.
I will not give the initialization code and details of Three.js itself, the network is full of information about this.
And our first step ...
Step 1 - Black and Black Background
First we need to make a substrate. This is done simply in one line:
renderer.setClearColor(0x000000);
It will look like this:
Obviously this is Malevich’s Rectangle, and this is where we finish the first step.
Isn't it really simple?
Step 2 - And the Sky Full of Stars
Malevich’s rectangle is just wonderful, but you still need to add stars.
Since we are making a map of the galaxy, we need a spiral similar to a galaxy. I chose a logarithmic spiral .
First, write down all the variables we need
//переменные для построения логарифмической спирали
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
We write an algorithm that is very simple: we go through the loop and calculate the coordinates of each star (the formula on Wikipedia) and shift them a bit randomly, so that it looks more like a galaxy.
//Строим логарифмическую спираль
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});
}
}
Since the number of points is very large, we arrange them with a system of particles, there will be much less lags:
//геометрия
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);
The addStar function at this stage:
var addStar = function(x, y) {
var v = new THREE.Vector3();
v.x = x * 10;
v.y = y * 10;
geometry.vertices.push(v);
}
And we did it ...
Something in the center of a hole, let's take a closer look:
Disgusting, but not difficult to fix, add two cycles.
The first cycle we generate a ring of points:
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});
}
The second cycle generates a circle of points:
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});
}
Great, we already have something like a galaxy. But we need names for our stars. You have% username%, but the star does not. Is it fair?
Step 3 - Star named% starname%
Well let's do it sequentially.
We need to make a function to generate the name. Need a global list of stars (coordinates + name). It is necessary to modify the addition of stars and include the generation of the name there. This is by the very presence of names. You also need a function to display the name of the star during the mouseover event. The problem is that since this is a system of particles that doesn’t hang an event, it means you need to come up with something else. There are many options, but I did the following: I found the JD implementation of KDTree, and drove into the tree all the points that we have (i.e. the same global list), and wrote the following in the mousemove event handler:
//класический способ перевода координат мыши в мировые координаты
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"));
}
Each time to recreate the mesh is not too optimal, of course, but performance is enough.
I won’t give all the code, who wants to be able to go to the github at the end and have a look, and before the next step we will look at the pictures:
Step 4 - Bringing Order to Chaos
So, we already have a galaxy, the names of stars, we can see them, but since we have a map, then we need a partition of space. If I say: “Listen, fly to the TX-82 system and buy me kefir,” it will not be clear where to fly, because a) it is not a fact that the system with the name TX-82 is the only one, b) how to find the system among over 20k stars ?
Let's make such a breakdown: there are quadrants, there are sectors. The whole galaxy is divided into 4 * 4 = 16 quadrants, 4 on each side. Each quadrant, in turn, is divided into 4 sectors. Those. we can address the system as a quadrant # qX-qY - sector (sx-sy) - system% starname%.
We do it with trivial lines, again, I don’t give the code, it’s big and not interesting - just calculating the coordinates of the beginning and end of each line. Who cares - welcome to github.
The result, as you might have guessed, is in the header of the article. But I will give another picture:
You just need to add the notation - the text, like (1-1), (3-3), (2-3). There is a grid, but no notation. Add.
Step 5 - Chip and Dale
Save from misunderstanding what these numbers are above in the picture. Or at least try. Yes. Two lines of HTML and CSS:
#x-y Quadrant(sx-sy) Sector
Step 6 - Where am I?
And the last thing that remains for us is to indicate our position in the galaxy. I hit a brick on the head, forgot where you are. But we need to tell where to bring kefir, what should we do? We open the map and thanks to the technologies:
Yes, the code is:
//Добавить маркер
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);
}
Conclusion
So, we made a map. I did not cite many things in the article, for example, shifting the map by holding the key, zoom, didn’t detail the work with Three.js very much, in my opinion it is secondary and not so interesting.
Github: github.com/MagistrAVSH/galmap
Demo works in the latest FF, Chrome, Opera. In IE11 it will work poorly, you will not see any labels at all, it crookedly supports WebGL. magistravsh.github.io/galmap
For the future there are ideas to write articles about the map of the star system, about the nebula generator, generators of various objects, and in general on the subject of fantastic space :) If you are interested, write.
Finally, under the spoiler, a couple more screenshots.
Screenshots