How to get random point near edges of a square in javascript

Tags: , , ,



I want to make a function that gives me a random point near the edges of a rectangle from a point. This is what I came up with so far, but I have absolutely no idea why it is not working.

function Point(x, y) {
  this.x = x;
  this.y = y;
}

function randomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function getRandomPointNearEdges(rectPos, width, height, border) {
  var point = new Point(rectPos.x, rectPos.y);
  if (randomNumber(0, 1) == 0) {
    point.x = randomNumber(rectPos.x, rectPos.x + border);
    if (randomNumber(0, 1) == 0) {
      point.y = randomNumber(rectPos.y, rectPos.y + border);
    }
    else {
      point.y = randomNumber(rectPos.y + height, (rectPos.y + height) + border);
    }
  }
  else {
    point.y = randomNumber(rectPos.y, rectPos.y + border);
    if (randomNumber(0, 1) == 0) {
      point.y = randomNumber(rectPos.x, rectPos.x + border);
    }
    else {
      point.y = randomNumber(rectPos.x + height, (rectPos.x + width) + border);
    }
  }
  return point;
};

window.onload = function() {
  canvas = document.getElementById("canvas");
  canvas.width = 700;
  canvas.height = 700;
  var ctx = canvas.getContext("2d");
  ctx.strokeRect(130, 130, 500, 500);
  
  for (var i = 0; i < 30; i++) {
    var point = getRandomPointNearEdges(new Point(130, 130), 500, 500, 100);
    ctx.fillRect(point.x, point.y, 2, 2);
  }
};
<canvas id="canvas"></canvas>

Just to clarify, the black region in this ‘Not to scale’ diagram is where I want to allow the point to generate. The width / height of that black region is the border property in the code snippet.

Diagram

Why is my function not working? Thanks in advanced.

Answer

Random with even distribution.

Just to point out that the answer by SimpleJ is statistical flawed with the distribution of random locations having a bias to the corners and then to the shorter sides, even though they cover much less area.

The ideal random location should be spread equally over the area in question, if the height of the box is less than the width then there is less chance of the the sides getting a point.

The example below provides a much faster and a much better distribution. I have added the given answers solution as well so you can compare.

The function that gets a random pos. The arguments x,y top left inside edge of rectangle, w,h inside width and height of the rectangle minDist, maxDist the min and max dist the random point can be from the inside edge of the box. You can also use negative values have the points outside the rectangle. Note that the distances are always from the inside edge of the box. The values are also floored when return (can easily be remove and still works)

function randomPointNearRect(x, y, w, h, minDist, maxDist) {
  const dist = (Math.random() * (maxDist - minDist) + minDist) | 0;
  x += dist;
  y += dist;
  w -= dist  * 2
  h -= dist  * 2
  if (Math.random() <  w / (w + h)) { // top bottom
    x = Math.random() * w + x;
    y = Math.random() < 0.5 ? y : y + h -1;
  } else {
    y = Math.random() * h + y;
    x = Math.random() < 0.5 ? x: x + w -1;
  }
  return [x | 0, y | 0];
}

Note there is a slight bias to the inside of the box. It can be removed with a little calculus with the bias rate of change f'(x) = 8*x 8 pixels per pixel inward and the anti derivative f(x)=4*(x**2) + c would directly relate to the distribution. Where x is dist from edge and c is related to perimeter length

Example to compare

The example has two canvases. Many random points are drawn. click the top canvas to add more points. Note how the bottom canvas sides and corners get darker due to the bias of the random points.

const ctx = canvas.getContext("2d");
canvas.onclick = ()=>{
  getRandomPointsForBox(200, box,4, 18);
  getRandomPoints(200);
}
const edgeClear = 30;
var box = {
  x: edgeClear,
  y: edgeClear,
  w: canvas.width - edgeClear * 2,
  h: canvas.height - edgeClear * 2,
  edge: 4,
}

function drawBox(box) {
  ctx.fillRect(box.x, box.y, box.w, box.h);
  ctx.clearRect(box.x + box.edge, box.y + box.edge, box.w - box.edge * 2, box.h - box.edge * 2);
}

function drawPixel(x, y) {
  ctx.fillRect(x, y, 1, 1);
}

function getRandomPointsForBox(count, box, min, max) {
  min += box.edge;
  max += box.edge;
  while (count--) {
    const [x, y] = randomPointNearRect(box.x, box.y, box.w, box.h, min, max);
    drawPixel(x, y);
  }
  
}

drawBox(box);
getRandomPointsForBox(200, box,4, 18);
ctx.font = "18px arial"
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText("Click to add more random points.",canvas.width / 2, canvas.height / 2);



function randomPointNearRect(x, y, w, h, minDist, maxDist) {
  const dist = (Math.random() * (maxDist - minDist) + minDist) | 0;
  x += dist;
  y += dist;
  w -= dist  * 2
  h -= dist  * 2
  if (Math.random() <  w / (w + h)) { // top bottom
    x = Math.random() * w + x;
    y = Math.random() < 0.5 ? y : y + h -1;
  } else {
    y = Math.random() * h + y;
    x = Math.random() < 0.5 ? x: x + w -1;
  }
  return [x | 0, y | 0];
}









/* The following is from the answer provided by SimpleJ https://stackoverflow.com/a/49581326/3877726 */

const ctx1 = canvas1.getContext('2d');

const rect = {
  x: box.x, y: box.y,
  width: box.w, height: box.h,
};

drawRect(rect);

ctx1.font = "18px arial"
ctx1.textAlign = "center"
ctx1.textBaseline = "middle"
ctx1.fillText("SimpleJ's method.",canvas1.width / 2, canvas1.height / 2);
ctx1.fillText("Note density of sides and corners.",canvas1.width / 2, canvas1.height / 2 + 20);

function getRandomPoints(count) {
  while (count--) {
    drawPoint(randomPointInRect(sample(rects)));
  }
}


var rects = getBorderRects(rect, 10);




function getBorderRects(rect, distance) {
  const { x, y, width, height } = rect;
  return [
    {x: x, y: y, width: width, height: distance}, // top
    {x: x, y: y + height - distance, width: width, height: distance}, // bottom
    {x: x, y: y, width: distance, height: height}, // left
    {x: x + width - distance, y: y, width: distance, height: height}, // right
  ];
}

function sample(array) {
  return array[Math.floor(Math.random() * array.length)];
}

function randomPointInRect({x, y, width, height}) {
  return {
    x: x + (Math.random() * width),
    y: y + (Math.random() * height),
  };
}
function drawRect({x, y, width, height}) {
  ctx1.strokeRect(x, y, width, height);
}
function drawPoint({x, y}) {
  ctx1.fillRect(x, y, 1,1);
}
  getRandomPoints(200);
<canvas id="canvas" width="500" height="200"></canvas>
<canvas id="canvas1" width="500" height="200"></canvas>


Source: stackoverflow