如何在3D空间中旋转一个向量?P5.js

5

这实际上是我在stackoverflow上的第一个问题 :)

首先,我想说我是编程新手,想要了解一些语法。基本上,我正在按照codetrain的教程创建面向对象的分形树,我想知道如何使它更加“三维” ,一种方法是通过旋转向量来实现。我一直在寻找潜在的解决方案来旋转下面的向量以在3D空间中使用。codetrain使用的示例是创建一个方向向量:direction = p5.Vector.Sub(this.begin, this.end)。然后他使用代码direction.rotate(45)。我意识到你不能写direction.rotateY(45)。从p5.js语法中我看到.rotate()只能用于2D向量。因此,是否有某种语法我忽略了可以根据以下代码将该向量在3D空间内旋转?

这是草图的代码。Branch类控制树的构造,branchA和branchB函数是可能添加一些旋转的地方。

var tree = [];
var leaves = [];
var count = 0;

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  angleMode(DEGREES)

  var randomangle = random(0, 90)
  var randomMinusAngle = random(0, -90)

  var a = createVector(0, 0, 0);
  var b = createVector(0, -100, 0);

  var root = new Branch(a, b);
  tree[0] = root;
}

//INPUT.

function mousePressed() {
  for (var i = tree.length - 1; i >= 0; i--) {
    if (!tree[i].finished) {
      tree.push(tree[i].branchA())
      tree.push(tree[i].branchB())
    }

    tree[i].finished = true;
  }

  count++
  if (count === 8) {
    for (var i = 0; i < tree.length; i++) {
      if (!tree[i].finished) {

        var leaf = tree[i].end.copy();

        leaves.push(leaf);
      }
    }
  }
}

function draw() {
  background(51);
  //orbitControl();
  rotateY(frameCount);
  translate(0, 200, 0);
  
  for (var i = 0; i < tree.length; i++) {
    tree[i].show();
    tree[i].rotateBranches();

    //tree[i].jitter();
  }

  //LEAVES
  for (var i = 0; i < leaves.length; i++) { //should  make the leaves an object in new script.
    fill(255, 0, 100);
    noStroke();
    ellipse(leaves[i].x, leaves[i].y, 8, 8);
  }
}

function Branch(begin, end) {
  this.begin = begin;
  this.end = end;

  //bool to check whether it has finished spawning branch.
  this.finishedGrowing = false;

  this.show = function() {
    stroke(255);

    line(this.begin.x, this.begin.y, this.begin.z, this.end.x, this.end.y, this.end.z);
  }

  //var rotateValue = random(0, 45)

  this.branchA = function() {
    var direction = p5.Vector.sub(this.end, this.begin);

    direction.rotate(45)
    direction.mult(0.67);

    var newEnd = p5.Vector.add(this.end, direction);

    var randomss = p5.Vector.random3D(this.end, newEnd)
    var b = new Branch(this.end, newEnd);

    return b;
  }
  
  this.branchB = function() {
    var direction = p5.Vector.sub(this.end, this.begin)

    direction.rotate(-15);

    direction.mult(0.67);
    var newEnd = p5.Vector.add(this.end, direction);

    var b = new Branch(this.end, newEnd);

    return b;
  }
  
  this.rotateBranches = function() {
    // TODO
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>


3
好的问题。我很愉快地回答了这个问题。希望能有所帮助。 - Paul Wheeler
1个回答

7
你没有错过任何东西。不幸的是,p5js没有提供一种在3D中旋转p5.Vector的方法(其他答案中提到的rotateX/rotateY/rotateZ函数会转换视图矩阵,从而改变图元在世界空间中绘制的位置,但这不是同一件事情,尽管它也可以用于以不同的3D方向绘制线条)。然而,你可以自己实现这个功能。有几种方法可以进行3D旋转:你可以使用欧拉角,你可以使用变换矩阵,你可以使用四元数。然而,我认为最容易理解的方法是轴角表示法(实际上四元数只是一种特殊的编码轴角旋转的方式,以便可以高效地操作和应用它们)。
在轴角表示法中,您通过指定一个轴来旋转向量,并指定一个角度来指示旋转的程度。

在这个示例gif中,绿色箭头代表轴向量,紫色箭头代表正在旋转的向量(在这种情况下,它恰好通过坐标系的每个轴)。
虽然可能不是最有效的方法,但有一个简单的算法来计算这样的旋转,称为 Rodrigues' rotation formula。给定一个向量v和一个轴向量k(必须是单位向量,意思是它的长度为1),公式如下:

注意:是向量叉积的符号,·是向量点积的符号。如果您不熟悉这些符号也没关系。只需要知道它们是数学上组合两个向量的方法,其中叉积产生一个新向量,而点积产生一个数值。以下是使用p5.js实现的公式:
// Rotate one vector (vect) around another (axis) by the specified angle.
function rotateAround(vect, axis, angle) {
  // Make sure our axis is a unit vector
  axis = p5.Vector.normalize(axis);

  return p5.Vector.add(
    p5.Vector.mult(vect, cos(angle)),
    p5.Vector.add(
      p5.Vector.mult(
        p5.Vector.cross(axis, vect),
        sin(angle)
      ),
      p5.Vector.mult(
        p5.Vector.mult(
          axis,
          p5.Vector.dot(axis, vect)
        ),
        (1 - cos(angle))
      )
    )
  );
}

好的,有了这个,我们如何将其应用于所讨论的草图中呢?目前,分支方向向量始终在XY平面上保持平坦,并且在分叉时始终围绕Z轴旋转。当我们开始让分支进入Z维度时,这变得有些复杂。首先,在分叉时,我们需要找到一个初始的旋转轴,它与当前分支垂直。我们可以使用叉积(参见findPerpendicular帮助函数)来实现这一点。一旦我们有了那个轴,我们就可以执行我们的固定“分叉”旋转,要么是45度,要么是-15度。接下来,我们将新的分支方向向量“扭曲”到原始分支方向周围,这就是使我们的分支进入Z维度的方法。

let tree = [];

function setup() {
  createCanvas(windowWidth, windowHeight, WEBGL);
  angleMode(DEGREES);

  const start = createVector(0, 0, 0);
  const end = createVector(0, -100, 0);

  tree[0] = new Branch(start, end);
}

function mousePressed() {
  // Add another iteration of branches
  for (let i = tree.length - 1; i >= 0; i--) {
    if (!tree[i].finished) {
      // Pick a random twist angle for this split.
      // By using the same angle we will preserve the fractal self-similarity while
      // still introducing depth. You could also use different random angles, but
      // this would produce some strange un-tree-like shapes.
      let angle = random(-180, 180);
      tree.push(tree[i].branchA(angle));
      tree.push(tree[i].branchB(angle));
    }

    tree[i].finished = true;
  }
}

function draw() {
  background(51);
  //orbitControl();
  rotateY(frameCount);
  translate(0, 200, 0);

  // Show all branches
  for (let i = 0; i < tree.length; i++) {
    tree[i].show();
  }
}

function Branch(begin, end) {
  this.begin = begin;
  this.end = end;

  //bool to check whether it has finished spawning branch.
  this.finishedGrowing = false;

  this.show = function() {
    stroke(255);

    line(this.begin.x, this.begin.y, this.begin.z, this.end.x, this.end.y, this.end.z);
  }

  this.branch = function(forkAngle, twistAngle) {
    let initialDirection = p5.Vector.sub(this.end, this.begin);

    // First we rotate by forkAngle.
    // We can rotate around any axis that is perpendicular to our current branch.
    let forkAxis = findPerpendicular(initialDirection);

    let branchDirection = rotateAround(initialDirection, forkAxis, forkAngle);

    // Next, rotate by twist axis around the current branch direction.
    branchDirection = rotateAround(branchDirection, initialDirection, twistAngle);
    branchDirection.mult(0.67);

    let newEnd = p5.Vector.add(this.end, branchDirection);
    return new Branch(this.end, newEnd);
  }

  this.branchA = function(twistAngle) {
    return this.branch(45, twistAngle);
  }

  this.branchB = function(twistAngle) {
    return this.branch(-15, twistAngle);
  }
}

function findPerpendicular(vect) {
  const xAxis = createVector(1, 0, 0);
  const zAxis = createVector(0, 0, 1);
  // The cross product of two vectors is perpendicular to both, however, the
  // cross product of a vector and another in the exact same direction is
  // (0, 0, 0), so we need to avoid that.
  if (abs(vect.angleBetween(xAxis)) > 0) {
    return p5.Vector.cross(xAxis, vect);
  } else {
    return p5.Vector.cross(zAxis, vect);
  }
}

function rotateAround(vect, axis, angle) {
  axis = p5.Vector.normalize(axis);
  return p5.Vector.add(
    p5.Vector.mult(vect, cos(angle)),
    p5.Vector.add(
      p5.Vector.mult(
        p5.Vector.cross(axis, vect),
        sin(angle)
      ),
      p5.Vector.mult(
        p5.Vector.mult(
          axis,
          p5.Vector.dot(axis, vect)
        ),
        (1 - cos(angle))
      )
    )
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>


1
哇,这个答案非常有帮助——不仅解决了问题,而且让我能够理解如何处理向量问题。真的非常感谢你的详细解答!我学到了很多。 - tommyk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接