Interstellar Travel for All — A Socratic Exchange

Tau Station
Cultured Perl
Published in
7 min readFeb 16, 2017
The Tau Station logo is set above the words “INTERSTELLAR TRAVEL FOR ALL” with a map of stars behind it. To the right is the outline of a person inside a circle, with a star map shown faintly behind it.

In one of our recent posts we described some of the work we’ve been doing to improve Tau Station’s star map. This week we’re going more in-depth about the process and will even show some of the code we’re using.

Initially, a canvas map was all we offered the player.

The <canvas>-based star map.

But as we said in the last map post, a canvas is only selectively accessible, and doesn’t work with screen readers or in browsers that don’t support JavaScript. Thus we wanted to reduce the star map into its basic HTML representation, so that we could progressively build it back up into a canvas rendering.

When we approached this, we asked ourselves one question:
What is this thing, and what does it do?

Ok, so maybe two questions.

What is this thing?

It’s a star map.

Be more precise…
It’s a map of the known Universe

Ok, and what’s on the map?
Stars. And wormholes.

What is the difference between a star and a wormhole?
A star is a location on the map. A wormhole is a bi-directional connection between two stars.

A star is a location on the map. A wormhole is a bi-directional connection between two stars.

Anything else?
Not that I can think of…

Okay. So all this is in essence, is a list of stars, and wormholes connecting them. Do I have that right?
Well, yes, if you put it that way…but stars have types, and stations-

We’ll get to that. But about those wormholes. Since a wormhole is bi-directional, given star *A* has wormhole *C*, and star *B* has wormhole *C*, stars *A* and *B* are connected by *C*. Is that right?
Yes.

What does it do?

It rotates.

Well yes, but only on the canvas. Why would someone want to see it? What can it be used for?
They might want to travel to a star.

Ah, so if there is a jump gate on your current station, you can jump to another star using the map?
Yes. But you have to select your destination star first.

What does that do?
It shows you details for the star you selected; its type, stations, and wormholes. It also gives you a jump option if you’re in the cockpit of your own ship or near the shuttles in a station’s port.

Ah. Anything else you’d use the map for when plotting a route?
Well, if a star’s stations are aligned with a government affiliation that you’re on good or bad terms with, it might influence your travel decision.

How would I know that?
The wormhole has a certain color, depending on the connected stars’ affiliations.

Representing it as HTML

So in essence, the map contains a list of stars.

<h2>Stars</h2> 
<ul class="stars">
<li class="star">…</li>
<li class="star">…</li>
<li class="star">…</li>

</ul>

Stars have a type, and a number of stations.

<h2>Stars</h2> 
<ul class="stars">
<li class="star">
<h3 id="alpha-centauri-a">Alpha Centauri A</h3>
<p>A G2-type star with 8 stations</p>
</li>
<li class="star">…</li>
<li class="star">…</li>

</ul>

They can also have wormholes. Semantically this makes the most sense as a nested list.

By giving each star h3 an id, we can then use local anchors to jump to a wormhole’s destination star.

This turns our list of stars into an interconnected list — which is what we had on the canvas as well.

<h2>Stars</h2> 
<ul class="stars">
<li class="star">
<h3 id="alpha-centauri-a">Alpha Centauri A</h3>
<p>A G2-type star with 8 stations</p>
<h4>Wormholes</h4>
<ul>
<li class="wormhole">
<a href="#ross-154">Huygens Conduit to Ross 154</a>
</li> <li class="wormhole">
<a href="#ross-128">Lagrange Conduit to Ross 128</a>
</li>
<li class="wormhole">
<a href="#l-143-23">Gate 61.JJ to L 143-23</a>
</li>
</ul>
</li>
<li class="star">…</li>
<li class="star">…</li>

</ul>

If you’re at a station with a jump gate, under certain conditions you can jump to another star.

<h2>Stars</h2> 
<ul class="stars">
<li class="star">
<h3 id="alpha-centauri-a">
Alpha Centauri A
<a href="/travel/alpha-centauri-a">Jump</a>
</h3>
<p>A G2-type star with 8 stations</p>
<h4>Wormholes</h4>

</li>
<li class="star">…</li>
<li class="star">…</li>

</ul>

The above HTML works. It renders a navigable map in a simple text browser, as well as a graphical browser. It can be styled with CSS. Excellent. Now for the progressive enhancement.

Adding map meta-data

You told me the canvas map rotates. How does it rotate?
Around an axis. Its y-axis.

So it’s rotating horizontally. Ok. That would mean that for each star we have to know its coordinates in 3-d space.
Yes. But we get those from the database.

Splendid. We can associate those via custom data attributes.

// These coordinates are relative to the rotation center, which is the star the player is on at that moment <li class="star" 
data-x-coordinate="x_coordinate_from_database"
data-y-coordinate="y_coordinate_from_database"
data-z-coordinate="z_coordinate_from_database">
<h3 id="alpha-centauri-a">Alpha Centauri A</h3>
<p>A G2-type star with 8 stations</p> … </li>

Since wormholes are simple connections between stars, all we need is a way to distinguish them.

<h4>Wormholes</h4> 
<ul>
<li class="wormhole" id="huygens-conduit">
<a href="#ross-154">Huygens Conduit to Ross 154</a>
</li>
<li class="wormhole" id="lagrange-conduit">
<a href="#ross-128">Lagrange Conduit to Ross 128</a>
</li>
<li class="wormhole" id="gate-61-jj">
<a href="#l-143-23">Gate 61.JJ to L 143-23</a>
</li>
</ul>

We can generate an internal hashmap of unique wormholes and plot them on the map based on their star pair.

Any other meta-information that we can display on the canvas map?
Well, we’d like to highlight the star that the player is currently on.

Good point. Actually, that’s also something we can indicate on the HTML list version… we should add that.

<li class="star" 
data-x-coordinate="x_coordinate_from_database"
data-y-coordinate="y_coordinate_from_database"
data-z-coordinate="z_coordinate_from_database"
data-is-current="is_current_from_database">
<h3 id="alpha-centauri-a">Alpha Centauri A</h3>
<p>A G2-type star with 8 stations</p>

</li>

Rendering the canvas

Some of the JavaScript used in rendering the map is based on the work of Sebastian Schaetz.

We start out with a canvas.

<canvas id="starmap"></canvas>

Then we add the original starmap DOM as a child, as fallback for user-agents which do not support canvas.

<canvas id="starmap"> 
<ul class="stars">
<li class="star">…</li>
<li class="star">…</li>
<li class="star">…</li>
… </ul>
</canvas>

Creating stars

As we traverse the DOM list of stars, we convert li.star elements into usable objects via a simple function.

var Star = function (props) { 
var self = {};
self.x = props.x; // the x-coordinate data-attribute
self.y = props.y; // and y
self.z = props.z; // and z
self.rgb = props.rgb // star color based on its type
//…more properties…
return self;
};

Apart from its coordinate data and other basic properties such as color, name, unique ID, and whether it’s reachable or not, we can also alter a star’s position in 3D space by rotating it around its axis via some unit circle trigonometry:

// The sine and cosine values are based on a radian of 0.01 // x, y and z coordinates for each star are relative to the star the player currently resides closest to. // rotate the star around an axis 
self.rotate = function (axis, sine, cosine) {
(_rotate_func[axis])(sine, cosine);
};
// rotate star around x axis
var _rotate_x = function (sine, cosine) {
var y = (self.y * cosine) + (self.z * (-sine));
self.z = (self.y * sine) + (self.z * (cosine));
self.y = y;
};
// rotate star around y
axis var _rotate_y = function (sine, cosine) {
var x = (self.x * cosine) + (self.z * sine);
self.z = (self.x * (-sine)) + (self.z * (cosine));
self.x = x;
};
// rotate star around z axis
var _rotate_z = function (sine, cosine) {
var x = (self.x * cosine) + (self.y * (-sine));
self.y = (self.x * sine) + (self.y * (cosine));
self.x = x;
};
// ROTATION is a map-global faux-enum object
var _rotate_func = {
ROTATION.X : _rotate_x,
ROTATION.Y : _rotate_y,
ROTATION.Z : _rotate_z
};

So now that we can rotate stars around an axis, let’s move on to getting the wormholes sorted out.

Creating wormholes

Since we established that a wormhole is just a connection between two stars, all it needs to know is which stars those are.

var Wormhole = function (props) { 
var self = {
from_star: props.from_star,
to_star : props.to_star
};
return self;
};
var some_wormhole = Wormhole(star_a, star_b);

Rendering the map

So, we have a canvas element. We can create stars and rotate them around an axis. We can create wormholes based on stars.

To handle the actual rendering, we have a Renderer abstraction that knows how to draw the actual elements on the map.

The Renderer is constructed with the dom canvas element as argument, and exposes some important functions.

clear_canvas: function () { }, 
draw_wormholes: function (wormholes, max_z) { },
draw_stars: function (stars, current_star, selected_star) { }, find_star: function (x, y, stars) { }, // mouse click

Stars

Internally, it knows how to plot a star’s position.

var _projection = function (star) { 
return {
x : ((_canvas_width / 2) + (star.x * _star_scale * _zscale
/ (star.z + _zscale)) | 0),
y : ((_canvas_height / 2) + (star.y * _star_scale * _zscale
/ (star.z + _zscale)) | 0),
z : 255 - ((((star.z * _star_scale + (_zscale/2)) *
_zhelper) + 0.5) | 0)
};
};

And uses that to draw a circle on the canvas.

var _draw_star = function (star, with_name, is_current, 
is_selected) {

var projection = _projection(star);
_draw_circle(projection.x, projection.y, radius,
star.rgb.join(',') );
};

Wormholes

In the case of a wormhole, all we need to know is the current positions of the two stars it connects.

var from_projection = _projection(from_star); 
var to_projection = _projection(to_star);

Then we can use that to plot a gradient line.

var gradient = canvas_context.createLinearGradient(from_projection.x, from_projection.y, to_projection.x, to_projection.y); 
… canvas_context.strokeStyle = gradient;
canvas_context.beginPath();
canvas_context.moveTo(from_projection.x, from_projection.y); canvas_context.lineTo(to_projection.x, to_projection.y);
canvas_context.stroke();

In the main Starmap abstraction itself, we use requestAnimationFrame on the canvas to render the map before each new rendering moment.

var draw = function (force_redraw) { window.requestAnimationFrame( function () { draw_map(force_redraw); }, renderer.canvas); };

Wrapping up

There’s a lot more code involved than what was show here. But hopefully this you gives an idea of how we’re rendering basic HTML and eventually end up with a rotating 3D canvas map.

Originally published at taustation.space on February 16, 2017.

--

--

Tau Station
Cultured Perl

Official page for TauStation. Uncover the plot that brought humanity to the edge of extinction in a text-based science fiction MMORPG. http://taustation.space/