Как получить случайную точку возле краев квадрата в JavaScript

Я хочу сделать функцию, которая дает мне случайную точку рядом с краями прямоугольника от точки. Это то, что я придумал до сих пор, но я понятия не имею, почему это не работает.

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>

Просто чтобы прояснить, черная область на этой диаграмме "Не в масштабе" - это то место, где я хочу разрешить создание точки. Ширина / высота этой черной области является свойством border во фрагменте кода.

схема

Почему моя функция не работает? Заранее спасибо.

2 ответа

Решение

Случайный с равномерным распределением.

Просто чтобы указать, что ответ SimpleJ является статистическим недостатком с распределением случайных местоположений, имеющих уклон к углам, а затем к более коротким сторонам, даже если они охватывают гораздо меньшую площадь.

Идеальное случайное местоположение должно быть равномерно распределено по рассматриваемой области, если высота прямоугольника меньше ширины, то вероятность того, что стороны получат точку, меньше.

Пример ниже обеспечивает намного более быстрое и намного лучшее распределение. Я добавил решение для данных ответов, чтобы вы могли сравнить.

Функция, которая получает случайный поз. Аргументы x, y верхний левый внутренний край прямоугольника, w, h внутренняя ширина и высота прямоугольника minDist, maxDist минимальное и максимальное расстояние до случайной точки может быть от внутреннего края поля. Вы также можете использовать отрицательные значения у точек вне прямоугольника. Обратите внимание, что расстояния всегда от внутреннего края коробки. При возврате значения также теряются (могут быть легко удалены и все еще работают)

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];
}

Обратите внимание, что внутри коробки есть небольшое смещение. Это может быть удалено с небольшим исчислением со скоростью изменения смещения f'(x) = 8*x 8 пикселей на пиксель внутрь и антипроизводная f(x)=4*(x**2) + c будет иметь непосредственное отношение к распределению. Где х от края и с относится к периметру

Пример для сравнения

В примере есть два холста. Многие случайные точки нарисованы. нажмите на верхний холст, чтобы добавить больше очков. Обратите внимание, как нижние стороны и углы холста становятся темнее из-за смещения случайных точек.

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://stackru.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>

Если вы думаете о проблеме получения случайной точки возле ребра, как о получении случайной точки в одном из четырех прямоугольников ребра, эту проблему гораздо легче решить:

  1. Получить краевые прямоугольники.
  2. Выберите случайный край прямоугольника.
  3. Создайте случайную точку в крае прямоугольника.

Чтобы сгенерировать прямоугольники ребер, нам нужно максимальное расстояние (как далеко от ребра может быть точка?):

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
  ];
}

Чтобы выбрать случайный прямоугольник из нашего массива краевых прямоугольников, мы можем определить sample функция:

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

Затем, чтобы выбрать случайную точку в прямоугольнике, нам просто нужно немного Math.random:

function randomPointInRect({x, y, width, height}) {
  return {
    x: x + (Math.random() * width),
    y: y + (Math.random() * height),
  };
}

И все вместе:

const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');

const rect = {
  x: 10, y: 20,
  width: 300, height: 200,
};

drawRect(rect);

drawPoint(
  randomPointInRect(
    sample(
      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}) {
  context.strokeRect(x, y, width, height);
}

function drawPoint({x, y}) {
  context.arc(x, y, 1, 0, Math.PI * 2);
  context.fill();
}
<canvas width="500" height="500"/>

Для любого здесь, как я, ищущего короткое и простое решение, этот пост наиболее близок к тому, что я обнаружил, что не говорит о тригонометрии.. Хотя то, что я придумал, может не быть прямым решением проблемы OP, возможно, кто-то сочтет это полезным..

Подход довольно прост.

Math.random()число от 0 до 800. Используйте модуль и разделите то, что осталось, на 200, чтобы получить случайную сторону и точку оси. Сдвиньте случайную сторону до упора, назначьте случайное значение другой оси и да, вот и все... вот пример:

let rndm = Math.floor(Math.random()*800-1);
let offset = rndm % 200;
let side = (rndm - offset) / 200; // 0:top 1:right 2:btm 3:left
let y = side % 2 > 0 ? offset+1 : 100 * side ;
let x = side % 2 < 1 ? offset+1 : 100 * (side - 1) ;

point.y = y - 100;
point.x = x - 100;

В моем случае мне понадобились как отрицательные, так и положительные значения с точкой отсчета. И если вы хотите создать точку внутри границы, просто введите другое случайное число, охватывающее ширину границы.

Только не забудьте отрегулировать углы.

offset += rndmBorder * 2;         // creates an inward line in the corners
point.x = x - 100 + rndmBorder;   // still keeping the origin point nice and center
                                _____________
                                |\_________/|  <-// inward line
                                | |       | |
                                | |       | |

Все, что мне было нужно, это компенсировать некоторые буквы... и большая часть того, что я обнаружил, показалось мне излишним... Это действительно работает довольно хорошо, надеюсь, это поможет.

Другие вопросы по тегам