Search code examples
three.js3dreact-three-fiberreact-three-drei

How to display a million of interactive line strings in Three.js


I would like to display a circuit using react-three-fiber (or with the Drei library).

  • A circuit is composed of segments and turns.
  • A segment is defined thanks to two points.
  • A turn is defined thanks to a list of points.

Segments and turns

I'd like my user to be able to construct the circuit by himself. It means that he will be able to modify (drag/drop/delete and so on...) the turns and the segments. It will result with around 10,000 of segments and turns and so in more than 100,000 points.

How would you proceed? (see the below for more details about my tests)


Considered Solutions and difficulties

Segments

  • r3f ((react-three-fiber) instanceMesh component (with a rectangle): I would need to store a map of the instanceId linked to my turn/segments ids, it does not seem impossible. It seems to be a good solution and is still performant with more than 100k elements. I implemented a quick example here to test this logic.

  • drei Instance component: With more than 1k elements, it starts to be laggy. You can play with an example here (adapted from an r3f example)

Turns

  • r3f instanceMesh component: The turn can be seen as a list of segments. With this solution, if we zoom too much, we can clearly see each segments. It is maybe not ideal, but it seems to be working. Another issue is that a turn can be composed of 100+points. If I have 1k turns, it will draw 100k segments, which starts being a lot

  • line: We could create only one geometry with all the turns inside. However, currently all my lines are linked (like if I was drawing them with a pen in one shot). Moreover, I do not think it is easily possible to manipulate a specific turn with this solution.

  • Line v2: We could also create as much geometries as turns. Each turn would be an entity instead of lots of segments. However, having 1K+ geometries is not recommended. With this solution, the performances were not good at all.

  • Limiting the number of objects: Another solution I would like to try is to draw all my turns in one geometry. In this case I would have no interaction. When zoomed enough, with only a maximum of 50 visible, I add real line with all the interactions. It is a bit tricky, but it also seems to be a good compromise.

The ideal would be to have an instance of the turns (such as the segments). However, each turn seems to be unique and I do not see how it would be possible to create only one geometry in an instance to display all the possible turns.


What would be the best way to implement this logic?


I tried this method by drawing only the closest turns. It stays quite fluid. I will try to go further with this solution: Here is a GIF example of the implementation:

Enter image description here


Solution

  • I made a PR for a LineSegments component, and the component is now available in Drei 9.53.0. You can use it like <Line segments />.

    This component can render lines (with gaps) and have a line width (other than 1px) in one call. That would probably be the most performant way of drawing it.

    The LineSegments is basically Drei's Line component, but using Three.js LineSegments2 instead of Line2. LineSegments2 or Line2 is what enables us to draw lines with a line width less than one pixel.

    For interaction, you can still use the raycaster. The pointer events of R3F also exposed a faceIndex which you can use to determine which line.

    If you want to draw various width, group your lines based on width, and create a LineSegments2 per width.

    If the frame rate is too low, combine it with LOD and frustum culling.

    Finally, your frame rate can also drop if the number of rerenders is high. Do add some console logs, for example, to see if you are rerendering a lot on the Canvas, but also outside of the Canvas! I've noticed that when I also have a high number of CPU cycles—not related to Three.js—rerender, calculation, etc., the frame rate also drops.