Adding 2D labels to a 3D scene

Here is the link to the 3D tool: https://sternenhimmel-der-menschheit.de/explore

Technical Implementation

The labels play an important role in the 3D scene since they let the user know where they currently are in the sky by showing the stars' names (the stars relevant to the current culture are written in the scene, but it's always possible to see individual stars' names by hovering them). It also gives more information on the current culture's night sky (since the tool is used to show constellations of different cultures around the world: Aborigines, Inuit... and the stories around them).

Therefore their position needs to be in sync with those elements. This was originally done by having an array of labels and updating their position individually by looping over them and getting the position based on the star they are linked to. This was sadly, not very good for performance which caused the labels to lag behind whenever the user navigated the 3D scene.

A member of our team found an example where the CSS2D labels were used in combination with Three.js (the library we're using for the 3D scenes). Since the labels of this example animate smoothly, I modified the code structure to use this new solution instead. This proved to be a great improvement especially since the number of labels per culture kept increasing afterwards.

Adding an icon to the label

While working on the tool, new needs appeared little by little with the addition of new cultures (which we implement as soon as their concept is ready). One of those new features was the possibility of adding a moon symbol at the end of some star labels.

Positioning the labels around the stars

While working with the 3D scene, I realized that positioning the labels in 3D causes more difficulties than in 2D since it’s hard to visualize if the labels are going to look good under every zoom level, and under every rotation angle. I therefore made a set of rules:

  • If multiple labels can be used on the same stars, they should be placed on all sides of it (since they will all rotate around and distance themselves from the same point, they will never overlap).

  • The same rule can regrettably not be used for different stars, since having one label above one star and the other under a nearby star will leave a good distance between them both under one angle, but if the scene is rotated upside down, they will end up overlapping.

  • Long names should be broken into multiple lines, to ideally imitate a square or round shape. This is because since the label will rotate, the space needed around them will be a circle with its diameter being the same size as the label’s width (or height, but generally the height is not a problem). By reducing its width by breaking it into multiple lines, this circle of safe space needed decreases in size.

Label opacity on zoom

The user can zoom in and out of the scene. This does not cause any issue for the 3D elements of the scene, since they can scale proportionally, but the labels can not scale in the same way, since they need to stay readable. Naturally, while zooming out, more labels will appear, since more of the scene is revealed. This causes multiple issues: the labels overlapping, the constellations being hidden by them and generally, the whole scene becoming very confusing due to information overload.

To solve this, the labels’ opacity will turn on or off at different zoom levels: the smallest ones, which indicate individual stars, will only be visible while looking at the constellations closely (since they only give additional information on the exact stars in the constellations anyway). The medium ones indicate to which constellation family (or which zone) the stars belong to and are also there to help the user orient him/herself in the sky, which will disappear a bit later. Then, the labels relevant to the current culture (showing the constellations’ names, individual star names, and the Milky Way) have individual settings and will only disappear if they are close to one another, to prevent overlapping.

Label size modified on Zoom

While it is not possible to scale the labels in the same way that the elements of the 3D scene scale up and down, (since they would become illegible), it is still possible to modify the size of the biggest ones slightly. The biggest labels therefore have their scale modified when the user zooms out so that they appear smaller (while the small ones disappear). Whenever the user zooms in or out, a regular expression checks if the label has a class name with FOV in it and a number inferior to the current FOV level. If that’s the case then its opacity is changed to 0.

Styling the labels

Those different needs for the star label size, color, position, whether they need to have a symbol next to it, the way its text aligns, and so on are managed by adding different CSS classes. They are added to the elements, when they are generated, with JavaScript. Some star labels have an ID, which corresponds to the constellation it is linked to. This determines whether it should disappear or not when a constellation is highlighted (which is also controlled via JavaScript).

Switching the labels’ opacity

A button also makes it possible to turn the opacity of all labels off, and then make them reappear afterwards.

Clicking on the constellation labels

Another feature request was to make it possible to hover the constellation’s labels and to make them clickable. I was not sure if this was going to be possible, since the constellation’s labels and the 3D scene are on 2 different layers, and to have both this highlight feature and all events in the 3D scene happening, both need to receive mouse events. However, this ended up working out fine, with the labels container having a pointer-events: none, while the labels can receive the event.

Debugging the labels' animation

Another issue appeared while adding the interaction with the labels: they were not properly following the position of the star they are linked to. I originally thought this might be a problem caused by the Three.js library. But after looking at the code in GitHub, it seemed that this could not be the case, since the labels’ position is simply controlled with a transform property added with JavaScript. After some research, I found out that this is because browsers have their default interaction styles, which have a higher priority than the styles normally applied, or those applied with JavaScript, which caused a conflict. I added “transition: none;” to the star labels, which solved the issue.

When the label’s opacity is turned off, its pointer events are also set to “none” so that it does not create any conflict with the scene.