vasturiano / d3-force-surface

A multi-surface elastic collision force type for the d3-force simulation engine
MIT License
21 stars 1 forks source link

Consider a more succinct/standard surface specification #4

Open Nate-Wessel opened 3 years ago

Nate-Wessel commented 3 years ago

Following the example from the readme, I've specified a bounding box for my simulation as follows:

const [ x1, x2, y1, y2 ] = [ -width/2, width/2, -height/2, height/2 ]
this.simulation.force('bounding-box',
    forceSurface().surfaces([
        {from:{x:x1,y:y1},to:{x:x2,y:y1}}, // top
        {from:{x:x1,y:y2},to:{x:x2,y:y2}}, // bottom
        {from:{x:x1,y:y1},to:{x:x1,y:y2}}, // left
        {from:{x:x2,y:y1},to:{x:x2,y:y2}} // right
    ]).radius(n=>n.radius).elasticity(0)
)

This works great (Yay!) but it also strikes me as very verbose and hard to read. In the geospatial world, we often specify bounding boxes as [[x1,y1],[x2,y2]] (i.e. [top-left],[bottom-right]).

Or alternatively an arbitrary polyline could be specified as a series of sequential points given as [x,y] coordinates. Thus my bounding box would be [[x1,y1],[x2,y1],[x2,y2],[x1,y2],[x1,y1]] with five points defining four surfaces.

It would be nice if these more succinct formats could be parsed as well. This could even allow some direct interactions with e.g. d3-geo, though I have a feeling this force would scale badly for complex (multi)polygons. Still, there might be some really interesting use cases there.

Anyway, just a friendly suggestion. This library is exactly what I was looking for - thanks for your work on this!

vasturiano commented 3 years ago

Hi @Nate-Wessel, thanks for reaching out!

That's great feedback, and I'm glad the force plugin does what you're looking for. 👍

You're right that it is a bit verbose for the common use case of setting a fully enclosed rectangular bounding box. However the syntax was made like that to allow the flexibility of setting surfaces in other scenarios, like having diagonal lines, non-rectangular shapes and even for cases when the surfaces are not necessarily enclosed, like in the quad-pong example.

At the end, it's prob easy enough to do a function that converts one syntax into the other, for the simple bounding case, like:

const bbox2Surfaces = ([[x1, y1], [x2, y2]]) => [
  { from: { x: x1, y: y1 }, to: { x: x2, y: y1 } },
  { from: { x: x2, y: y1 }, to: { x: x2, y: y2 } },
  { from: { x: x2, y: y2 }, to: { x: x1, y: y2 } },
  { from: { x: x1, y: y2 }, to: { x: x1, y: y1 } }
];

We could maybe simplify the syntax of each line from { from: {x1,y1}, to: {x2,y2}} to something like [[x1,y1],[x2,y2]]. But it's normally so easy to get lost in these nested arrays and I think spelling out the fields can prevent some bugs from creeping, and help with troubleshooting. Once again you can also easily do a structures transformation helper method. We could make the lib accept both, but that may add a bit of confusion, I don't know.

Oh, and for the simple case of keeping nodes within a bounding box, there may be an easier force for that: d3-force-limit, with also an easier input syntax for coords. 😄

There's an example here: https://observablehq.com/@vasturiano/d3-force-limit

Nate-Wessel commented 3 years ago

Thanks for the reply @vasturiano!

I did eventually find my way over to d3-force-limit which was actually more what I was looking for for this particular application. More succinct and also works great!

One thing I might suggest for this lib is looking into the GeoJSON spec. I could see this handling simple lines, polylines, polygons, multipolygons and even geometry collections pretty well if the shapes were simple enough. And there are lots of tools already for working with geometry data in those formats. The coordinates would just have to be in screen pixels rather than geographic coordinates.