Как мне сделать так, чтобы этот массив кругов отображался в пределах границы параллелограмма?

#random #geometry #p5.js

Вопрос:

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

Код от p5.js:

 let circles = [];

function setup() {
  createCanvas(1200, 1200);
  colorMode(HSB,360,100,100,1);
  
  
  for (let i = 0; i < 500; i  ) {
    x = random(400, 800);
    y = random(400, 800);
    d = 10;
    circles[i] = new circleClass(x, y, d);
  }
}


function draw() {
  
  background(35,13,90,1);
  
  noLoop();

  for (let i = 0; i < circles.length; i  ) {
    circles[i].show();
  }
  
  
  noFill();
  stroke(0);
  quad(400,600,800,200,800,600,400,1000);
  
}



class circleClass {
  constructor(x, y, d) {
    this.x = x;
    this.y = y;
    this.d = d;
  }
  show() {
    noStroke();
    fill(27, 71, 73, 1);
    ellipse(this.x, this.y, this.d);
  }
}
 

Ответ №1:

Похоже, вы хотите генерировать маленькие круги в случайных положениях внутри параллелограмма.

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

 ax = v[1].x - v[0].x  (800-400 for your quad)
ay = v[1].y - v[0].y  (200-600 for your quad)
bx = v[2].x - v[0].x   (and so on)
by = v[2].y - v[0].y

t = random(0..1)
u = random(0..1)
x = v[0].x   ax * t   bx * u
y = v[0].y   ay * t   by * u
 

где v[] массив вершин параллелограмма

 let v = [400,600,800,200,800,600,400,1000]
let ax = v[2] - v[0]
let ay = v[3] - v[1]
let bx = v[4] - v[0]
let by = v[5] - v[0]

for loop {
    let t = Math.random()
    let u = Math.random()
    let x = v[0]   ax * t   bx * u
    let y = v[1]   ay * t   by * u
    set circle center in x, y
 

Вот это p5.js версия:

 function setup() {
  createCanvas(600, 600);
  background(255);
  
  let v = [200, 300,   // top left     array indices: [x=0, y=1]
           400, 100,   // top right    array indices: [x=2, y=3]
           400, 300,   // bottom right array indices: [x=4, y=5]
           200, 500];  // bottom left  array indices: [x=6, y=7]
  
  // top right - top left
  let ax = v[2] - v[0];
  let ay = v[3] - v[1];
  // bottom left - top left
  let bx = v[6] - v[0];
  let by = v[7] - v[1];

  for(let i = 0; i < 500; i  ) {
      let t = random(); // interpolation amount on 1 axis
      let u = random(); // interpolation amount on the other
      // offset by top left point (v[0], v[1])
      // interpolate along top left to top right (imagine X axis aligned with parallelogram sides)
      // and top left to bottom left (imagine Y axis aligned with parallelogram sides)
      let x = v[0]   (ax * t   bx * u);
      let y = v[1]   (ay * t   by * u);
      // render point
      circle(x, y, 10);
  }
  
  noFill();
  // pass the v array as arguments using the spread operator
  quad(...v);
} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> 

Комментарии:

1. Спасибо! можете ли вы показать мне, как это будет вписываться в предоставленный мной код?

2. @astromango стоит попытаться понять логику приведенного выше фрагмента: таким образом можно узнать гораздо больше. Вот несколько указателей: Как упоминал MBo, «v [] — это массив вершин параллелограмма», поэтому начните с этого, используя свои четырехугольные координаты( 400,600,800,200,800,600,400,1000 ). (например v = [{x:400, y:600},{x:800,y:200},... , и так далее). В p5 случайным является значение от 0 до 1 random(0, 1) . как только у вас будут координаты x,y, вы сможете использовать их для своих экземпляров circle. Может возникнуть забавная задача: принять во внимание диаметр круга. Не торопись, ты все понял!

3. @astromango Я не силен в js, поэтому просто псевдокодирую

4. спасибо @GeorgeProfenza. Я попробую это сделать. Я потратил на это около часа до сих пор безрезультатно, я думаю, что мне нужно немного научиться основам, прежде чем браться за это дело!

5. @Джордж Профенца, Все в порядке. Я считаю, что в моем случае псевдокод лучше, чем некачественная попытка сделать допустимый JS-код 😉

Ответ №2:

Это дополнение к ответу MBo выше ( 1).

Суть заключается в этом разделе:

 let x = v[0]   ax * t   bx * u
let y = v[1]   ay * t   by * u
 

Это может выглядеть более знакомо как уравнение прямой.

В p5.js уже существует очень полезная lerp() функция, которая принимает два числа и третий параметр t между 0,0 и 1,0 и возвращает число между первыми двумя (например, если t равно 0,5, результат будет 1/2 между числами, 0,25 будет 1/4 и .75 будет 3/4 и т. Д.). Вот основной пример:

 let v1, v2, v3, v4;

function setup() {
  createCanvas(600, 600);
  
  v1 = createVector(200, 300);
  v2 = createVector(400, 100);
  v3 = createVector(400, 300);
  v4 = createVector(200, 500);
}

function draw(){
  background(255);
  
  let t = map(constrain(mouseX, 0, width), 0, width, 0.0, 1.0);
  // interpolate each component, x,y between v1 and v2 using horizontal mouse movement
  let x = lerp(v1.x, v2.x, t);
  let y = lerp(v1.y, v2.y, t);
  // preview interpolated position
  circle(x, y, 10);
  
  noFill();
  stroke(0);
  quad(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y);
} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> 

Мы могли бы использовать это напрямую, но это было бы утомительно делать 8 раз (2 координаты (x,y) для 4 точек (квадрата)).

В p5 также есть очень полезный p5.Vector класс с a p5.Vector.lerp() , который может помочь легко интерполировать между двумя точками:

 let v1, v2, v3, v4;

function setup() {
  createCanvas(600, 600);
  
  v1 = createVector(200, 300);
  v2 = createVector(400, 100);
  v3 = createVector(400, 300);
  v4 = createVector(200, 500);
}

function draw(){
  background(255);
  
  let t = map(constrain(mouseX, 0, width), 0, width, 0.0, 1.0);
  // interpolate each component, x,y between v1 and v2 using horizontal mouse movement
  let l = p5.Vector.lerp(v1, v2, t);
  // preview interpolated position
  circle(l.x, l.y, 10);
  
  noFill();
  stroke(0);
  quad(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y);
} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> 

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

 /**
* Bilinear interpolation: interpolates a point on a between two lines (defined by 4 points)
* @param xt: traversal on first axis (0.0 -> 1.0)
* @param yt: traversal on second axis (0.0 -> 1.0)
**/
function quadLerp(v1, v2, v3, v4, xt, yt){
    let v1to2 = p5.Vector.lerp(v1, v2, yt);
    let v3to4 = p5.Vector.lerp(v3, v4, yt);
    return p5.Vector.lerp(v1to2, v3to4, xt);
}
 

ДЕМОНСТРАЦИЯ:

 let v1, v2, v3, v4;

function setup() {
  createCanvas(600, 600);
  
  v1 = createVector(200, 300);
  v2 = createVector(400, 100);
  v3 = createVector(400, 300);
  v4 = createVector(200, 500);
}

/**
* Bilinear interpolation: interpolates a point on a between two lines (defined by 4 points)
* @param xt: traversal on first axis (0.0 -> 1.0)
* @param yt: traversal on second axis (0.0 -> 1.0)
**/
function quadLerp(v1, v2, v3, v4, xt, yt){
    let v1to2 = p5.Vector.lerp(v1, v2, yt);
    let v3to4 = p5.Vector.lerp(v3, v4, yt);
    // text() is simply for debugging/visualisation purposed: not actually required
    text("v1to2", v1to2.x, v1to2.y);
    text("v3to4", v3to4.x, v3to4.y);
    return p5.Vector.lerp(v1to2, v3to4, xt);
}

function draw(){
  background(192, 255, 192);
  
  let u = map(constrain(mouseX, 0, width), 0, width, 0.0, 1.0);
  let v = map(constrain(mouseY, 0, height), 0, height, 0.0, 1.0);
  // vertex order (winding) is important
  let l = quadLerp(v1, v2, v4, v3, u, v);
  // preview interpolated position
  circle(l.x, l.y, 10);
  
  noFill();
  stroke(0);
  quad(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y);
} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> 

Обратите внимание, что при перемещении мыши по всему холсту эскиза круг всегда остается в пределах квадрата.

Это в значительной степени решение: просто поменяйте отображенные координаты мыши на случайные числа в диапазоне от 0.0 до 1.0.

 function setup() {
  createCanvas(1200, 1200);
  background(192, 255, 192);
  
  let v1 = createVector(400, 600);
  let v2 = createVector(800, 200);
  let v3 = createVector(800, 600);
  let v4 = createVector(400, 1000);
  
  for (let i = 0 ; i < 500; i  ) {
      let u = random();
      let v = random();
      // vertex order (winding) is important
      let l = quadLerp(v1, v2, v4, v3, u, v);
      circle(l.x, l.y, 10);
  }
  
  noFill();
  stroke(0);
  quad(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y);
}

/**
* Bilinear interpolation: interpolates a point on a between two lines (defined by 4 points)
* @param xt: traversal on first axis (0.0 -> 1.0)
* @param yt: traversal on second axis (0.0 -> 1.0)
**/
function quadLerp(v1, v2, v3, v4, xt, yt){
    let v1to2 = p5.Vector.lerp(v1, v2, yt);
    let v3to4 = p5.Vector.lerp(v3, v4, yt);
    return p5.Vector.lerp(v1to2, v3to4, xt);
} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> 

Вот вариант, более близкий к вашему коду:

 let circles = [];

function setup() {
  createCanvas(1200, 1200);
  colorMode(HSB,360,100,100,1);
  background(35,13,90,1);
  
  let v1 = createVector(400, 600);
  let v2 = createVector(800, 200);
  let v3 = createVector(800, 600);
  let v4 = createVector(400, 1000);
  
  for (let i = 0 ; i < 500; i  ) {
      let u = random();
      let v = random();
      // vertex order (winding) is important
      let l = quadLerp(v1, v2, v4, v3, u, v);
      circles[i] = new Circle(l.x, l.y, 10);
      circles[i].show();
  }
  
  noFill();
  stroke(0);
  quad(v1.x, v1.y, v2.x, v2.y, v3.x, v3.y, v4.x, v4.y);
}

/**
* Bilinear interpolation: interpolates a point on a between two lines (defined by 4 points)
* @param xt: traversal on first axis (0.0 -> 1.0)
* @param yt: traversal on second axis (0.0 -> 1.0)
**/
function quadLerp(v1, v2, v3, v4, xt, yt){
    let v1to2 = p5.Vector.lerp(v1, v2, yt);
    let v3to4 = p5.Vector.lerp(v3, v4, yt);
    return p5.Vector.lerp(v1to2, v3to4, xt);
}

class Circle {
  constructor(x, y, d) {
    this.x = x;
    this.y = y;
    this.d = d;
  }
  show() {
    noStroke();
    fill(27, 71, 73, 1);
    ellipse(this.x, this.y, this.d);
  }
} 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> 

Я сделал пару настроек:

  • переименовано circleClass в простое Circle сохранение соглашения об именовании имени класса регистра заголовка. Я удалил эту Class часть, чтобы избежать возможной путаницы при создании экземпляров кругов (например new circleClass() )
  • Я удалил draw() , следовательно, устранил необходимость в noLoop();
  • circles массив также является своего рода избыточным в этом базовом примере, но может быть полезен в более подробной анимированной версии этого эскиза.

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

Комментарии:

1. Этот ответ действительно невероятен. Большое вам спасибо за то, что нашли время, чтобы так подробно описать решение, показать всю вашу работу и объяснить всю логику. Это было невероятно полезно!

2. Рад, что это помогает, и рад услышать подробные объяснения помощи. Имейте в виду, что, хотя моя версия, надеюсь, проста для понимания, она не самая эффективная (например, для каждой точки создается 3 экземпляра p5.Vector. (например, если вы запускаете это в интерактивном draw() режиме, это не эффективно использует память. Обратите внимание, что есть p5.js фрагмент теперь в нижней части ответа MBo: это более эффективно для памяти.