Let’s say you are trying to find the distance between two points in 2D, but that these points are in a universe that “wraps around” like old video games – leaving the screen on the right, left, top or bottom side makes you re-appear on the opposite edge.

This universe is actually shaped like a toroid, also known as a doughnut. It’s actually an impossible object, a “flat torus”, so not exactly a doughnut, but whatever.

If you imagine yourself on the surface of a doughnut, it would behave exactly this way. If you go “down” you end up where you previously considered “up”. If you go far enough “left” you end up where you previously considered “right”.

How would you calculate the distance between two points in a universe like this?

Let’s imagine the situation below where we are trying to find the distance between the red point and the green point:

One way to do this would be to pick one of the points (I’m picking red in this case) and clone it 8 times to surround the cell like the below. You’d calculate the distance from the green point to each of the 9 red points, and whatever distance was smallest would be the answer.

Something not so desirable about this is that it takes 9 distance calculations to find the minimum distance. You can work with squared distances instead of regular distances to avoid a square root on each of these distance calculations, but that’s still a bit of calculation to do.

Going up in dimensions makes the problem even worse. In 3D, it requires 27 distance calculations to find the shortest point, and 81 distance calculations in 4D!

Luckily there’s a better way to approach this.

Let’s say that our universe (image) is 1 unit by 1 unit big (aka we are working in texture UVs). If you look at the image with 9 copies of the red dot, you can see that they are just the 9 possible combinations of having -1, +0, +1 on each axis added to the red dot’s coordinates. All possible combinations of the x and y axis having -1, +0 or +1 added to them are valid locations of the red dot.

Looking at the distance formula we can see that if we minimize each axis individually, that we will also end up with the minimal distance overall.

So, the better way is to minimize each axis individually.

On the x axis you’d find if the x axis distance between the red and green point is minimal when you subtract 1 from the red dot’s x axis position, leave it alone, or add 1.

Whichever x axis value of the red dot gives you the minimal x axis 1D distance is the x axis location to use.

You’d repeat for the y axis to get the y axis location to use (and would repeat for any further axes for higher dimensions).

This gives you the closest point which you can then plug into the distance formula to get the distance between the points in this wrap around space.

You can actually do better though.

Still working on each axis individually, you can calculate the absoluate value of the 1D distance between the two points on that axis. If that distance is greater than 0.5, the real distance for that axis is 1-distance.

The intuition here is that if you are in a 1d repeating space, if going from A to B is more than half the distance, it means that you went the wrong way, and that going the other way is shorter. The distance of that other way is one minus whatever distance you just calculated since the distance from one point to itself is 1!

Do that for each axis and use those 1d distances in the distance formula to get the actual distance.

This lets you minimize the distance without having to explicitly figure out which combination makes the point closest.

More importantly, it lets you efficiently calculate the distance between the two points in toroidal space (doughnut space!)

The computational complexity is a lot better. It’s now linear in the number of dimensions: , instead of .

Here is some C++ to show you how it would work in 2D.

float ToroidalDistance (float x1, float y1, float x2, float y2) { float dx = std::abs(x2 - x1); float dy = std::abs(y2 - y1); if (dx > 0.5f) dx = 1.0f - dx; if (dy > 0.5f) dy = 1.0f - dy; return std::sqrt(dx*dx + dy*dy); }

I hit this problem trying to make a tileable texture. I needed to place a few circles on a texture such that the circles weren’t too close to each other, even when the texture was tiled.

The calculations above gave me the basic tool needed to be able to calculate distances between points. Subtracting circle radii from the distance between points let me get toroidal distance between circles and make sure I didn’t place them too closely to each other.

That let me make an image that kept the distance constraints even when it was tiled.

Here’s an example image by itself:

Here is the image tiled:

Pingback: Calculating the Distance Between Points in “Wrap Around” (Toroidal) Space | ExtendTree

Pingback: Calculating the Distance Between Substances in "Wrap Around" (Toroidal) Home | A1A

Reblogged this on josephdung.

LikeLike

what the fuck

LikeLike

I am translating this article to read it.

Can I also publish the translation on my website?

LikeLike

Yes, feel free to do so!

LikeLike

Thank you, I published Japanese translation!

https://kotet.github.io/2017/10/04/calculating-the-distance-between-points-in-wrap-around-toroidal-space.html

LikeLiked by 1 person

Pingback: Generating Blue Noise Sample Points With Mitchell’s Best Candidate Algorithm « The blog at the bottom of the sea

Hi, great blog !

Just a quick note: a flat torus is not impossible, as shown recently by some frenchies:

https://www.newscientist.com/article/dn21760-wrinkled-doughnut-solves-geometrical-mystery/

(The french article is also nice: http://images.math.cnrs.fr/Gnash-un-tore-plat.html)

UV

LikeLike

Pingback: Pulling The Plug, Part 10: Linear Love – Glyffe News

Pingback: Generating Blue Noise Textures With Void And Cluster « The blog at the bottom of the sea

Distance to a point in wrapped space does not have to be only to the closest instance. How about the square root of the mean squared distance to all instances in the infinite repetition grid? Is that defined and finite? (Not practical for your use case, of course. Nice tiles!)

LikeLike

Really interesting thought. My usage case ultimately was to make points in space that were randomized but roughly evenly spaced. I did that by generating a number of candidate points randomly (uniform white noise distribution) and picking the one with the largest closest distance to another point. I wonder how using your metric would affect the result? (better? worse? no different?)

LikeLike

If you add points one by one, I guess a metric that is sensitive to multiple cell distances may be better. Hm.

LikeLike

Thanks! My brain was breaking when attempting to do this logic for determining the optimal direction and # of steps to take for a stepper motor, based on a given destination angle and its current position. This also may have had something to do with it being 1am while working on it haha.

Here’s what I ended up doing:

char cw = 1; // Clockwise 1, ccw if 0.

int dx = stepperDestination-stepperPos;

if (dx 100){ // 200 steps in the stepper motor, check if we’re over halfway

dx = 200 – dx;

cw = !cw;

}

rotate( dx, cw /*1 for cw, 0 for ccw*/); // rotate to proper position

LikeLiked by 1 person

Nice. I’ve been trying to find the shortest vector that points from one point to the other. Other than cloning one point 8 times and then calculating all 8 vectors and selecting the shortest one I did not find a solution yet. Do you have an idea how to do this more efficiently?

LikeLike

edit: would be 9 vectors obviously

LikeLike

Is it that you have 2 points on a square, where the square “wraps around” and you want to find the shortest vector between those 2 points?

If so, yeah, it’s real similar to the steps in this post. I’m assuming the square goes from 0 to 1 on each axis, like in this post.

1) First you subtract the x axis components of the 2 points to get a distance on the x axis. If it’s greater than 1/2, you subtract that distance from 1 to get the shorter distance. That is the x component of your “shortest vector from A to B”

2) Repeat this for each of the other axes (y if 2d, y and z if 3d, etc)

3) Normalize the vector and you are done!

LikeLike