如何使用 Processing 在 P3D 中卷起 2D 网格

How to roll up a 2D Grid in P3D with Processing

我已经构建了一个带有嵌套循环的二维矩形网格。现在我想在三维 space 中“卷起”这个网格,或者换句话说“形成一个圆柱体”或“列”。随着我鼠标指针的移动。在“汇总”之前,我根据需要对所有内容进行了编程 - 但随后我的数学失败了。

float size;
float pixel = 75;

void setup() {
  size(1920, 1080, P3D);
  frameRate(30);
  size = width/pixel;
  rectMode(CENTER);
  noStroke();
}

    void draw() {
      background(0);
      rotateX(radians(45));
      translate(pixel*size/2, -pixel*size, -pixel*size);
      translate(-pixel*size/2, -pixel*size/2, -pixel*size/4);
      pushMatrix();
      for (int x = 0; x < pixel; x++) {
        for (int y = 0; y < pixel; y++) {
          pushMatrix();
          float sin = sin(radians(x * 10)) * mouseX;
          float cos = cos(radians(x * 10)) * mouseX;
          translate(x*size, y*size);
          rotate(radians(45));
          fill(255);
          rect(sin, cos, size/5, size/5);
          popMatrix();
        }
      }
      popMatrix();
    }

网格不是卷起,而是扭曲了两次....我想我可以通过连接 sin(); 来实现“卷起”;和余弦(); - 类似于这个例子:

float sin, cos;
void setup() {
  size(900, 900);
  background(0);
}
void draw() {
  translate(width/2, height/2);
  for (int i = 0; i < 200; i++) {
    sin = sin(radians(frameCount + i *10)) * 400;
    cos = cos(radians(frameCount + i *10)) * 400;
    ellipse(sin, cos, 10, 10);
  }
}

实现此汇总的最佳方法是什么?

您使用极坐标系到笛卡尔坐标系的转换公式是正确的。

有多种方法可以解决这个问题。 这是一个想法,首先从 2D 开始:将圆展开成一条线。 我不知道这样做在数学上是 100% 正确的方法,我希望其他人 post 这样做。然而,我可以 post 使用这些“成分”进行足够令人信服的估算:

  • 圆(周长)的长度为2πR
  • Processing 的 lerp() 在两个值(函数的前两个参数)之间按百分比(表示 0.0 和 1.0 之间的值(称为归一化值))进行线性插值 -> 0 = 0% = 起始值, 0.5 = 50% = half-way 在标准值和最终值之间,1.0 = 100% = 在最终值)
  • Processing 提供了一个 PVector class 既可以方便地封装 2D/3D 点属性 (.x, .y, .z),又提供了一个 lerp() 方法,它是一个很好 [=7​​9=] 避免手动 lerping 3 次(每个维度 (x, y, z) 一次)

这里有一个基本的注释草图来说明上面的内容:

// total number of points
int numPoints = 24;
// circle radius
float radius = 50;
// circle length (circumference) = 2πR
float circleLength = TWO_PI * radius;
// spacing between each point for the length of the circle
float lengthIncrement = circleLength / numPoints;
// how many radians should each point on a circle increment by
float angleIncrement = TWO_PI / numPoints;

// cache for points on a the circle
PVector[] pointsCircle = new PVector[numPoints];
// cache for points on a line the lenght of the circle
PVector[] pointsLine   = new PVector[numPoints];

void setup(){
  size(300, 300);
  noStroke();
  // cache: pre-compute start(circle) and end(line) points
  for(int i = 0 ; i < numPoints; i++){
    // compute the angle using the increment but also offset by 90 degrees so 1st point is at bottom
    float angle = (angleIncrement * i) + HALF_PI;
    pointsCircle[i] = new PVector(cos(angle) * radius, sin(angle) * radius);
    // compute positions on a line, offsetting by half: avoids most self-intersections when animating
    pointsLine[i] = new PVector(lengthIncrement * i - (circleLength * 0.5), 0);
  }
}

void draw(){
  background(0);
  translate(width * 0.5, height * 0.5);
  // map interpolation amount to mouse X position
  float interpolationAmount = (float)mouseX / width;
  // for each point
  for(int i = 0 ; i < numPoints; i++){
    // compute the interpolated position
    PVector pointAnimated = PVector.lerp(pointsCircle[i], pointsLine[i], interpolationAmount);
    // optional: visualise the first point as the darkest and last point as the brightest
    fill(map(i, 0, numPoints -1, 64, 255));
    // render the point as a circle
    circle(pointAnimated.x, pointAnimated.y, 9);
  }
}

可以在 3D 中应用相同的逻辑,使用额外的循环来重复 circles/lines 以显示为 cylinder/grid:

// total number of points
int numPointsX = 24;
// circle radius
float radius = 50;
// circle length (circumference) = 2πR
float circleLength = TWO_PI * radius;
// spacing between each point for the length of the circle
float lengthIncrement = circleLength / numPointsX;
// how many radians should each point on a circle increment by
float angleIncrement = TWO_PI / numPointsX;

// cache for points on a the circle
PVector[] pointsCircle = new PVector[numPointsX];
// cache for points on a line the lenght of the circle
PVector[] pointsLine   = new PVector[numPointsX];
// number of points on Z axis 
int numPointsZ = 24;

void setup(){
  size(300, 300, P3D);
  // render circles as thick points
  noFill();
  strokeWeight(9);
  // cache: pre-compute start(circle) and end(line) points
  for(int i = 0 ; i < numPointsX; i++){
    // compute the angle using the increment but also offset by 90 degrees so 1st point is at bottom
    float angle = (angleIncrement * i) + HALF_PI;
    pointsCircle[i] = new PVector(cos(angle) * radius, sin(angle) * radius);
    // compute positions on a line, offsetting by half: avoids most self-intersections when animating
    pointsLine[i] = new PVector(lengthIncrement * i - (circleLength * 0.5), 0);
  }
}

void draw(){
  background(0);
  
  translate(width * 0.5, height * 0.5, 0);
  rotateY(map(mouseX, 0, width, -PI, PI));
  rotateX(map(mouseY, 0, height, PI, -PI));
  
  float interpolationAmount = (float)mouseX / width;
  // render the grid (circular or rectangular)
  beginShape(POINTS);
  for(int j = 0 ; j < numPointsZ; j++){
    // offset by half the size to pivot from center
    float z = (circleLength * 0.5) - (lengthIncrement * j);
    
    for(int i = 0 ; i < numPointsX; i++){
      // compute the interpolated position
      PVector pointAnimated = PVector.lerp(pointsCircle[i], pointsLine[i], interpolationAmount);
      // render point
      stroke(map(i, 0, numPointsX -1, 64, 255));
      vertex(pointAnimated.x, pointAnimated.y, z);
    }
  }
  endShape();
}

以上代码在没有 PVector 的情况下也能正常工作,但会更冗长。 另一件要记住的事情是,静态 PVector.lerp() 方法将在每次调用时生成一个新的 PVector 实例:这对于像这样的小型演示是可以的,但是将一堆 PVector 缓存到 lerp() 应该会浪费更少的内存。

为了完整起见,这里是交互式版本,您可以 运行 就在这里通过 p5.js:

// total number of points
let numPoints = 24;
// circle radius
let radius = 50;
// circle length (circumference) = 2πR
let circleLength;
// spacing between each point for the length of the circle
let lengthIncrement;
// how many radians should each point on a circle increment by
let angleIncrement;

// cache for points on a the circle
let pointsCircle = new Array(numPoints);
// cache for points on a line the lenght of the circle
let pointsLine   = new Array(numPoints);

function setup(){
  createCanvas(300, 300);
  noStroke();
  // ensure TWO_PI is defined before assignment
  circleLength = TWO_PI * radius;
  lengthIncrement = circleLength / numPoints;
  angleIncrement = TWO_PI / numPoints;
  // cache: pre-compute start(circle) and end(line) points
  for(let i = 0 ; i < numPoints; i++){
    // compute the angle using the increment but also offset by 90 degrees so 1st point is at bottom
    let angle = (angleIncrement * i) + HALF_PI;
    pointsCircle[i] = createVector(cos(angle) * radius, sin(angle) * radius);
    // compute positions on a line, offsetting by half: avoids most self-intersections when animating
    pointsLine[i] = createVector(lengthIncrement * i - (circleLength * 0.5), 0);
  }
}

function draw(){
  background(0);
  translate(width * 0.5, height * 0.5);
  // map interpolation amount to mouse X position
  let interpolationAmount = constrain(mouseX, 0, width) / width;
  // for each point
  for(let i = 0 ; i < numPoints; i++){
    // compute the interpolated position
    let pointAnimated = p5.Vector.lerp(pointsCircle[i], pointsLine[i], interpolationAmount);
    // optional: visualise the first point as the darkest and last point as the brightest
    fill(map(i, 0, numPoints -1, 64, 255));
    // render the point as a circle
    circle(pointAnimated.x, pointAnimated.y, 9);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

// total number of points
let numPointsX = 24;
// circle radius
let radius = 50;
// circle length (circumference) = 2πR
let circleLength;
// spacing between each point for the length of the circle
let lengthIncrement;
// how many radians should each point on a circle increment by
let angleIncrement;

// cache for points on a the circle
let pointsCircle = new Array(numPointsX);
// cache for points on a line the lenght of the circle
let pointsLine   = new Array(numPointsX);
// number of points on Z axis 
let numPointsZ = 24;

function setup(){
  createCanvas(600, 600, WEBGL);
  // ensure TWO_PI is defined before assignment
  circleLength = TWO_PI * radius;
  lengthIncrement = circleLength / numPointsX;
  angleIncrement = TWO_PI / numPointsX;
  // render circles as thick points
  noFill();
  strokeWeight(9);
  stroke(255);
  // cache: pre-compute start(circle) and end(line) points
  for(let i = 0 ; i < numPointsX; i++){
    // compute the angle using the increment but also offset by 90 degrees so 1st point is at bottom
    let angle = (angleIncrement * i) + HALF_PI;
    pointsCircle[i] = createVector(cos(angle) * radius, sin(angle) * radius);
    // compute positions on a line, offsetting by half: avoids most self-intersections when animating
    pointsLine[i] = createVector(lengthIncrement * i - (circleLength * 0.5), 0);
  }
}

function draw(){
  background(0);
  orbitControl();
  rotateX(HALF_PI);
  let interpolationAmount = constrain(mouseX, 0, width) / width;
  // render the grid (circular or rectangular)
  beginShape(POINTS);
  for(let j = 0 ; j < numPointsZ; j++){
    // offset by half the size to pivot from center
    let z = (circleLength * 0.5) - (lengthIncrement * j);
    
    for(let i = 0 ; i < numPointsX; i++){
      // compute the interpolated position
      let pointAnimated = p5.Vector.lerp(pointsCircle[i], pointsLine[i], interpolationAmount);
      // render point
      vertex(pointAnimated.x, pointAnimated.y, z);
    }
  }
  endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

更新

如前所述,在这种简单的情况下,您可以在没有 PVector 的情况下工作:

// total number of points
int numPointsX = 24;
// circle radius
float radius = 50;
// circle length (circumference) = 2πR
float circleLength = TWO_PI * radius;
// spacing between each point for the length of the circle
float lengthIncrement = circleLength / numPointsX;
// how many radians should each point on a circle increment by
float angleIncrement = TWO_PI / numPointsX;

// cache for points on a the circle
float[][] pointsCircle = new float[numPointsX][2];
// cache for points on a line the lenght of the circle
float[][] pointsLine   = new float[numPointsX][2];
// number of points on Z axis 
int numPointsZ = 24;

void setup(){
  size(300, 300, P3D);
  // render circles as thick points
  noFill();
  strokeWeight(9);
  // cache: pre-compute start(circle) and end(line) points
  for(int i = 0 ; i < numPointsX; i++){
    // compute the angle using the increment but also offset by 90 degrees so 1st point is at bottom
    float angle = (angleIncrement * i) + HALF_PI;
    pointsCircle[i] = new float[]{cos(angle) * radius, sin(angle) * radius};
    // compute positions on a line, offsetting by half: avoids most self-intersections when animating
    pointsLine[i] = new float[]{lengthIncrement * i - (circleLength * 0.5), 0};
  }
}

void draw(){
  background(0);
  
  translate(width * 0.5, height * 0.5, 0);
  rotateY(map(mouseX, 0, width, -PI, PI));
  rotateX(map(mouseY, 0, height, PI, -PI));
  
  float interpolationAmount = (float)mouseX / width;
  // render the grid (circular or rectangular)
  beginShape(POINTS);
  for(int j = 0 ; j < numPointsZ; j++){
    // offset by half the size to pivot from center
    float z = (circleLength * 0.5) - (lengthIncrement * j);
    
    for(int i = 0 ; i < numPointsX; i++){
      // compute the interpolated position
      float pointAnimatedX = lerp(pointsCircle[i][0], pointsLine[i][0], interpolationAmount);
      float pointAnimatedY = lerp(pointsCircle[i][1], pointsLine[i][1], interpolationAmount);
      // render point
      stroke(map(i, 0, numPointsX -1, 64, 255));
      vertex(pointAnimatedX, pointAnimatedY, z);
    }
  }
  endShape();
}

就我个人而言,我觉得 PVector 版本的可读性稍微好一些。