如何在 3D 空间中旋转矢量? P5.js

Posted

技术标签:

【中文标题】如何在 3D 空间中旋转矢量? P5.js【英文标题】:How would I rotate a vector in 3D space? P5.js 【发布时间】:2021-07-31 03:47:07 【问题描述】:

这实际上是我关于 *** 的第一个问题 :)

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

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

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
  
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"&gt;&lt;/script&gt;

【问题讨论】:

好问题。我很高兴回答这个问题。希望对您有所帮助。 【参考方案1】:

你没有错过任何东西。不幸的是,p5js 没有提供一种在 3d 中旋转 p5.Vector 的方法(另一个答案中提到的 rotateX/rotateY/rotateZ 函数转换了视图矩阵,它改变了世界空间图元的绘制位置,但是这不是一回事,尽管它也可以用于在不同的 3d 方向上绘制线条)。但是,您可以自己实现。有几种方法可以进行 3d 旋转:可以使用Euler angles,可以使用transformation matrices,可以使用Quaternions。但是,我发现最容易理解的方法是axis-angle representation(四元数实际上只是一种特殊的编码轴角旋转的方法,以便可以有效地操纵和应用它们)。

在轴角表示中,您可以通过指定要围绕其旋转的轴和指示旋转距离的角度来旋转矢量。

在本例 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))
      )
    )
  );
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"&gt;&lt;/script&gt;

【讨论】:

哇,这个答案非常有帮助 - 不仅可以解决问题,还因为现在我可以理解如何处理诸如此类的矢量问题。真的非常感谢!我学到了很多。

以上是关于如何在 3D 空间中旋转矢量? P5.js的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 如何从3D矢量计算轴旋转或旋转矩阵

如何在 OpenGL 中绘制 3D 矢量?

我们如何在 3D 空间中同时旋转 2 个 UIView 平面

如何在opengl的3d空间中旋转单个形状?

基于HT for Web矢量实现3D叶轮旋转

如何计算倾斜的3D矢量的上升?