0

Let's say I have a player located at: X: 100, Y: 100, Z: 100and I want to find which of the following points is the closest and get it's ID.:

ID: 1, X: 200; Y: 200; Z: 100,
ID: 2, X: 150; Y: 300; Z: 300,
ID: 3, X: 300; Y: 200; Z: 100,
ID: 4, X: 50; Y: 100; Z: 200

How could I do it? What are the maths behind it? If it helps, I already have the following code:

var returnVehicles = [];
        mp.vehicles.forEachInRange(player.position, 100, (vehicle) => {
                if(vehicle.ownerID == player.id) {
                    returnVehicles.push(vehicle.position, vehicle.id);
                }
            }
        );

It loops through the vehicles in a range of a 100 and adds into an array the IDs and positions of the ones that belong to the player. However, I don't know what to do with this.

Someone recommend me the .sort() method, but that doesn't seem to work, as it only gets the smallest coordinate, not the nearest.

@EDIT: MY FULL CODE

function distance3D(posA, posB) {
    const dX = posA.X - posB.X;
    const dY = posA.Y - posB.Y;
    const dZ = posA.Z - posB.Z;
    return Math.sqrt(dX * dX + dY * dY + dZ * dZ);
}

const vehiclesAndDistances = [];

function lockPlayerVehicle (player) {
    let vehicle = player.vehicle;
    if (vehicle) {
        //IRRELEVANT
    }
    else {
        mp.vehicles.forEachInRange(player.position, 40, (vehicle) => {
                if(vehicle.ownerID == player.id) {
                    vehiclesAndDistances.push({vehicle, distance: distance3D(player, vehicle),});
                }
            }
        );
        vehiclesAndDistances.sort((a, b) => a.distance - b.distance);
        player.outputChatBox("Lista Pequena: " + String(vehiclesAndDistances[0]));
        console.log(vehiclesAndDistances[0], vehiclesAndDistances[0].vehicle.model)
    };
}
mp.events.add("keypress:DOWNARROW", lockPlayerVehicle);
Hugo Almeida
  • 105
  • 7

4 Answers4

1

For a small number of vehicles, simply using the Pythagorean algorithm to find the distance between the vehicle and the player is enough. (For more than a few (or if you need to loop this often) you could need to look into a space-partitioning algorithm such as quadtrees to make the lookup more effective.)


// Assumes posA and posB are both objects with X, Y, Z members.
function distance3D(posA, posB) {
  const dX = posA.X - posB.X;
  const dY = posA.Y - posB.Y;
  const dZ = posA.Z - posB.Z;
  return Math.sqrt(dX * dX + dY * dY + dZ * dZ);
}

// Stores objects of shape `{vehicle: ..., distance: number}`
const vehiclesAndDistances = [];

mp.vehicles.forEachInRange(player.position, 100, (vehicle) => {
  if (vehicle.ownerID == player.id) {
    vehiclesAndDistances.push({
      vehicle,
      distance: distance3D(player, vehicle),
    });
  }
});
// Sort the array by the distance
vehiclesAndDistances.sort((a, b) => a.distance - b.distance);

EDIT: As discussed in the comments, if you only need the nearest point, you can reformulate this as

let closestDistance = undefined;
let closestVehicle = undefined;

mp.vehicles.forEachInRange(player.position, 100, (vehicle) => {
  if (vehicle.ownerID === player.id) {
    const distance = distance3D(player, vehicle);
    if (closestDistance === undefined || distance < closestDistance) {
      closestVehicle = vehicle;
      closestDistance = distance;
    }
  }
});
AKX
  • 152,115
  • 15
  • 115
  • 172
  • 1
    @ggorlen Sure - we could even implement this by keeping track of the current minimum distance and vehicle within the `mp.vehicles.forEachInRange` loop and only ever store one, but this is easier to understand. – AKX Sep 15 '20 at 15:45
  • 1
    @ggorlen In fact, I added just that now. – AKX Sep 15 '20 at 15:47
  • Hi. I don't need the nearest point, I need the object that is in the nearest point. I've tried using the code above, but it always gets the same vehicle as of my testing and I got this on the console once I used `console.log(vehiclesAndDistances[0], vehiclesAndDistances[0].model):` `{ vehicle: { setVariable: [Function (anonymous)], setVariables: [Function (anonymous)], ownerID: 0 vehModel: 12566235 }, distance: NaN } undefined` The code knows the object and it's owner, but I can't seem to do nothing with it. In undefined I tried vehicle.model – Hugo Almeida Sep 15 '20 at 16:09
  • 1
    You're probably looking for `vehiclesAndDistances[0].vehicle.model`. – AKX Sep 15 '20 at 16:10
  • 1
    (However, as you can see from that output, the distance is `NaN`, so there's something else wrong too; maybe the coordinates aren't stored in `.X`/`.Y`/`.Z`?) – AKX Sep 15 '20 at 16:12
  • Ahh! That was it! Thank you very much! Sorry I'm still learning! – Hugo Almeida Sep 15 '20 at 16:12
  • About the positions, I didn't understand what you're saying. I copied the function you gave me. I actually switches them to lowercase letters, but I changed them back to uppercase now, and I still get same NaN. To get an objects positions I've always used entity.position (vehicle.position, in this case) and I always worked with X, Y, Z. – Hugo Almeida Sep 15 '20 at 16:17
  • I've edited the post with my full code. I can't seem to understand what's wrong – Hugo Almeida Sep 15 '20 at 16:22
  • @HugoAlmeida Then you'll need to call `distance3D(player.position, vehicle.position)` :) – AKX Sep 15 '20 at 16:26
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/221524/discussion-between-hugo-almeida-and-akx). – Hugo Almeida Sep 15 '20 at 16:36
1

You can use the distance formula. Apply it to each point and choose the minimum. Time complexity is linear, but you're probably running this in a loop per frame, so the overall complexity is quadratic. There are optimizations available. See

Possible micro-optimizations that don't change the time complexity include avoiding the square root operation, saving the min in one pass, etc.

const dist = (a, b) => Math.sqrt(
  (b.X - a.X) ** 2 +
  (b.Y - a.Y) ** 2 +
  (b.Z - a.Z) ** 2
);

const closest = (target, points, eps=0.00001) => {
  const distances = points.map(e => dist(target, e));
  const closest = Math.min(...distances);
  return points.find((e, i) => distances[i] - closest < eps);
};

const player = {X: 100, Y: 100, Z: 100};
const points = [
  {ID: 1, X: 200, Y: 200, Z: 100},
  {ID: 2, X: 150, Y: 300, Z: 300},
  {ID: 3, X: 300, Y: 200, Z: 100},
  {ID: 4, X: 50,  Y: 100, Z: 200}
];

console.log(closest(player, points));

You can generalize dist to work in any dimension as follows:

const dist = (a, b) => Math.sqrt(
  Object.keys(a).map(k => (b[k] - a[k]) ** 2)
    .reduce((a, e) => a + e)  
);
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • As an aside, using the spread operator (`...`) may not work with sufficiently large arrays since some JavaScript engines have a maximum argument count. (This is documented e.g. in the MDN.) – AKX Sep 15 '20 at 15:50
  • 2
    I'm aware of that, but the time complexity will become prohibitive long before that's a problem. You need to have tens of thousands of elements to hit that limit. OP hasn't stated they have a performance issue of that scale yet so it seems premature to optimize beyond plain old linear complexity. – ggorlen Sep 15 '20 at 15:53
  • Hi. This is also not working. console.log returns undefined. – Hugo Almeida Sep 15 '20 at 17:05
  • Works fine for me when I click "Run code snippet". If you want code that works for your use case more precisely, please provide a [mcve] that I can execute in full. Otherwise, it's up to you to adapt what I've shown to your codebase--the typical expectation in SO is that you'll need to do some extrapolation and adaptation on the conceptual understanding, at least for large/complex applications that this likely is. – ggorlen Sep 15 '20 at 17:06
0

You can use euclidean distance calculating formula. Put all the coordinates in an array and use map to create a new array of distance which is derived by implementing euclidean formula. You can sort the element of the array in ascending or descending order and find the distance

let currData = [{
    ID: 1,
    X: 200,
    Y: 200,
    Z: 100
  },
  {
    ID: 2,
    X: 150,
    Y: 300,
    Z: 300
  },
  {
    ID: 3,
    X: 300,
    Y: 200,
    Z: 100
  },
  {
    ID: 4,
    X: 50,
    Y: 100,
    Z: 200
  }
]

const vehlPos = {
  X: 250,
  Y: 400,
  Z: 600
};

let getDist = currData.map((item) => {
  const xdiff = Math.pow((vehlPos.X - item.X), 2);
  const ydiff = Math.pow((vehlPos.Y - item.Y), 2);
  const zdiff = Math.pow((vehlPos.Z - item.Z), 2);
  return Math.sqrt(xdiff + ydiff + zdiff)
});

console.log(getDist)
brk
  • 48,835
  • 10
  • 56
  • 78
  • @ggorlen this is just to show to calculate the distance SO user can modify the array as required to get the `id` which is a pretty small deal. & I have corrected my mistake – brk Sep 15 '20 at 15:54
  • 1
    If it's a pretty small deal, I'd go all the way and include it to provide OP a complete solution. But then the answer would be identical to the existing two answers. – ggorlen Sep 15 '20 at 15:57
0

You could convert your objects to points and use vector functions.

Here is a somewhat-complete example of a 3D vector class.

const main = () => {
  const player = { X: 100, Y: 100, Z: 100 };
  const points = [
    { ID: 1, X: 200, Y: 200, Z: 100 },
    { ID: 2, X: 150, Y: 300, Z: 300 },
    { ID: 3, X: 300, Y: 200, Z: 100 },
    { ID: 4, X:  50, Y: 100, Z: 200 }
  ];

  console.log(findClosestPoint(player, points));
};

const findClosestPoint = (player, points) => {
  let { X, Y, Z } = player;
  const pos = new Vector3D(X, Y, Z),
    minDist = points.reduce((res, point, index) => {
      let { X, Y, Z } = point;
      const value = pos.distanceTo(new Vector3D(X, Y, Z));
      return value < res.value ? { index, value } : res;
    }, { index: -1, value: Number.MAX_VALUE });
  console.log('Min distance:', minDist.value);
  return points[minDist.index];
};

class Vector3D {
  constructor (x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  distanceTo (other) {
    return Math.sqrt(
      Math.pow(this.x - other.x, 2) +
      Math.pow(this.y - other.y, 2) +
      Math.pow(this.z - other.z, 2)
    );
  }
}

main();
.as-console-wrapper { top: 0; max-height: 100% !important; }

Output

Min distance: 111.80339887498948
{
  "ID": 4,
  "X": 50,
  "Y": 100,
  "Z": 200
}
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132