#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 до 1random(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: это более эффективно для памяти.