how to calculate the distance between points on a 2D map if the world is looped (as in civilization)?

Just calculating the distance is not a problem. In my implementation, this is:

let distance = Math.sqrt((players[socketId].x - players[clientId].x)**2 + (players[socketId].y - players[clientId].y)**2);

But there is a nuance, my world is looped. This means that when reaching the edge of the map, the player changes the coordinate to the opposite one.

if (object.x >= config.worldSize || object.x <= -config.worldSize) object.x = -object.x;
if (object.y >= config.worldSize || object.y <= -config.worldSize) object.y = -object.y;

That is, if 2 players follow each other and one passes the edge of the world, then it will disappear from the field of view of the second, until he also passes the edge, since when "teleporting" the distance according to the formula increases by more than the length of the world itself, and the players are already considered far from each other. Example:

Both players go on the x coordinate to the right, the length of the world in one direction is 1000. When the first one is on 799 and the second one is on 999, they see each other, after 2 steps, the first one is on 801 and the second one is on -999, and the distance between them is already 1800, and they respectively do not see each other.

How can I make a formula so that they remain visible, taking into account such an organization of the world?

Author: Kromster, 2019-12-18

2 answers

Imagine a ring.

The distance between two points on it, can not be more than half the length of the circle.

If it is larger, it means going closer in the opposite direction.

enter a description of the image here

This is what we will use to determine the delta from one dimension:

// max - размер мира по одному измерению
const dist = (x0, x1, max) => {
    const v = Math.abs(x1 - x0);
    return v < max / 2 ? v : max - v;
} 

let drag;

const on = (types, cb) => types.forEach(type => addEventListener(type, cb));

const attr = (el, n) => +el.getAttribute(n);

const dist = (x0, x1, max) => {
  const v = Math.abs(x1 - x0);
  return v < max / 2 ? v : max - v;
}

const mirror = (v, size) => v < 0 ? size + (v % size): v % size;

const lerp = (p0, p1, t, size) => {
    let dist = (p1 - p0) % size;
    return p0 + (2*dist % size - dist)*t;
}

const w = attr(svg, 'width');
const h = attr(svg, 'height');

const calcDist = () => {

  const x1 = attr(p0, "cx"), 
      y1 = attr(p0, "cy"),
      x2 = attr(p1, "cx"), 
      y2 = attr(p1, "cy"),
      dx = dist(x1, x2, w);
      dy = dist(y1, y2, h);
      d = Math.sqrt(dx*dx + dy*dy), 
      n = parseInt(d/2.5);
      
  result.innerHTML = 'drag the circles, distance: ' + d.toFixed(1);
  
  dots.innerHTML = [...Array(n)].map((e, i) => 
    `<circle r=2 fill=steelblue
           cx=${mirror(lerp(x1, x2, i/n, w), w)} 
           cy=${mirror(lerp(y1, y2, i/n, h), h)} 
    />`).join('');
}

on(['mousedown', 'touchstart'], e => {
  e = e.touches ? e.touches[0] : e;
  const x = +e.target.getAttribute('cx') - e.clientX;
  const y = +e.target.getAttribute('cy') - e.clientY;
  if (!x && !y) return;
  drag = {x, y, element: e.target};
});

on(['mousemove', 'touchmove'], e =>  {
  if (!drag) return;
  e.preventDefault();
  e = e.touches ? e.touches[0] : e;
  drag.element.setAttribute('cx', mirror(drag.x + e.clientX, w));  
  drag.element.setAttribute('cy', mirror(drag.y + e.clientY, h));  
  calcDist()
});

on(['mouseup', 'touchend'], e => drag = null);

calcDist();
body {
  margin:0;
  overflow:hidden;
}

svg {
  user-select: none; 
  border: solid
}

text {
  font-size:20
}

circle {
  cursor: pointer;
}
<svg id=svg width=625 height=190>
    <g pointer-events=none>
        <text y=20 x=10 id=result></text>
        <g id=dots></g>
    </g>
    <circle id=p0 cx=66 cy=111 r=15 fill=#1594 />
    <circle id=p1 cx=166 cy=122 r=15 fill=#951a />
</svg>
 6
Author: Stranger in the Q, 2020-09-23 07:58:06

Generally speaking, this happens not only when crossing the border. Your world can be represented approximately in this form (the "world" itself is one rectangle, the rest is its copies): enter a description of the image here

So it makes sense to iterate over the distances not only to the object within one "world", but also to the nearest copies of it in neighboring "worlds" and choose the smallest. You can optimize a little, and not look at 9 copies, but be limited to a slightly smaller number - for example, if the object is on the right, then it makes no sense to consider even more distant worlds on the right (see the picture).

"In my opinion, so " (c) Pooh

 6
Author: Harry, 2019-12-19 09:35:49