在 Processing 中为 PMatrix3D 创建一个 rotate3D() 函数

Creating a rotate3D() function for PMatrix3D in Processing

前段时间,我仅基于 CSS 转换编写了一个有点烦躁的徽标。

你可以 fiddle 超过 https://document.paris/

效果感觉不错,click/touch拖动旋转logo感觉很自然

我记得我用头撞墙,直到我发现我可以很容易地链接 CSS 变换,只需链接它们即可。

transform: matrix3d(currentMatrix) rotate3d(x, y, z, angle);

最重要的是,为了获得 currentMatrix,我会简单地用 jQuery 做 m = $('#logobackground').css('transform');,浏览器会神奇地 return 计算矩阵而不是原始矩阵“css " 这实际上避免了我处理矩阵或无限堆叠 rotate3D() 属性。

所以最难的部分是根据鼠标输入计算 rotate3D 参数(x、y、z、角度)。理论上,将这部分转换为 java 应该没有问题,所以我将跳过它。

现在

我正在尝试使用 Processing 做完全相同的事情,但有两个问题:

  1. 处理中没有rotate3D()。
  2. 没有浏览器可以自动 apply/chain 转换和 return 当前矩阵状态。

这是我正在处理的 plan/implementation :

我需要一个“currentMatrix”来将每一帧应用于场景

PMatrix3D currentMatrix = new PMatrix3D();

setup() 中,我将其设置为“单位矩阵”,根据我的理解,这相当于“无转换”。

// set currentMatrix to identity Matrix
currentMatrix.set(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);

每一帧我都会计算一个变换矩阵并将其应用于 currentMatrix。 然后我会把这个矩阵应用到场景中。

// Apply Matrix to the currentMatrix
void mouseRotate() {
    float diag = sqrt(pow(width,2)+pow(height,2));
    float x = deltaX()/ diag * 10; // deltaX = difference between previous prevous MouseX and current mouseX)
    float y = deltaY()/ diag * 10; // deltaY = same with Y axis
    float angle = sqrt( pow(x, 2) + pow(y, 2) );
    currentMatrix.apply( rotate3D(y,x,0,angle) );
}
// Apply Matrix to the scene
applyMatrix(currentMatrix);

PMatrix3D 参考 : https://processing.github.io/processing-javadocs/core/processing/core/PMatrix3D.html

ApplyMatrix() 参考https://processing.org/reference/applyMatrix_.html

然后我需要做的就是将 rotate3D css 变换实现为一个函数,该函数 return 是一个变换矩阵。

基于我在此页面上找到的内容 https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d()

我实现了第一个功能:

PMatrix3D rotate3D(float x, float y, float z, float a) {
    PMatrix3D rotationMatrix = new PMatrix3D();
    rotationMatrix.set(
        1+(1-cos(a))*(pow(x,2)-1),  z*sin(a)+x*y*(1-cos(a)),    -y*sin(a)+x*z*(1-cos(a)),   0,
        -z*sin(a)+x*y*(1-cos(a)),   1+(1-cos(a))*(pow(y,2)-1),  x*sin(a)+y*z*(1-cos(a)),    0,
        y*sin(a)+x*z*(1-cos(a)),    -x*sin(a)+y*z*(1-cos(a)),   1+(1-cos(a))*(pow(z,2)-1),  0,
        0,0,0,1
    );
    return rotationMatrix;
}

并根据我在此页面上找到的内容 https://drafts.csswg.org/css-transforms-2/#Rotate3dDefined 我实现了这个其他功能:

PMatrix3D rotate3Dbis(float getX, float getY, float getZ, float getA) {
    float sc = sin(getA/2)*cos(getA/2);
    float sq = pow(sin(getA/2),2);
    float normalizer = sqrt( pow(getX,2) + pow(getY,2) + pow(getZ,2) );
    float x = getX/normalizer;
    float y = getY/normalizer;
    float z = getZ/normalizer;

    PMatrix3D rotationMatrix = new PMatrix3D();
    rotationMatrix.set(
        1-2*(pow(y,2)+pow(z,2))*sq, 2*(x*y*sq-z*sc),            2*(x*z*sq+y*sc),            0,
        2*(x*y*sq+z*sc),            1-2*(pow(x,2)+pow(z,2))*sq, 2*(y*z*sq-x*sc),            0,
        2*(x*z*sq-y*sc),            2*(y*z*sq+x*sc),            1-2*(pow(x,2)+pow(y,2)*sq), 0,
        0,                          0,                          0,                          1
    );
    return rotationMatrix;
}

测试时,它们不会用相同的输入产生完全相同的结果(尽管差异有点“对称”,这让我认为它们至少在某种程度上是等价的?)还有 rotate3Dbis () 倾向于生成 NaN 数字,尤其是当我不移动鼠标时 (x & y = 0)。

但最重要的是,最后还是不行。当我使用 rotate3D() 时,绘图不会旋转,而是逐渐缩小,并且 rotate3Dbis() 由于 NaN 而无法正确呈现。

总题: 我试图从了解转换矩阵的人那里获得指导,并试图缩小问题所在的范围。我对 rotate3D() 的 processing/java 实现是否存在缺陷?或者问题会来自其他地方吗?我的 rotate3D()rotate3Dbis 函数是否等效?

部分答案是添加到第一个 rotate3D 函数的修复程序。 我需要标准化 x、y、z 值以避免奇怪的缩放。

我发布了代码的当前状态(为了简单起见,我跳过了一些部分):

// Mouse movement since last fame on X axis
float deltaX() {
    return (float)(mouseX-pmouseX);
}

// Mouse movement since last fame on Y axis
float deltaY() {
    return (float)(mouseY-pmouseY);
}

// Convert user input into angle and amount to rotate to
void mouseRotate() {
    double diag = Math.sqrt(Math.pow(width,2)+Math.pow(height,2));
    double x = deltaX()/ diag * 50;
    double y = -deltaY()/ diag * 50;
    double angle = Math.sqrt( x*x + y*y );
    currentMatrix.apply( rotate3D((float)y,(float)x,0,(float)angle) );
}

// Convert those values into a rotation matrix
PMatrix3D rotate3D(float getX, float getY, float getZ, float getA) {
    float normalizer = sqrt( getX*getX + getY*getY + getZ*getZ );
    float x = 0;
    float y = 0;
    float z = 0;
    if (normalizer != 0) {
        x = getX/normalizer;
        y = getY/normalizer;
        z = getZ/normalizer;
    }
    float x2 = pow(x,2);
    float y2 = pow(y,2);
    float z2 = 0;
    float sina = sin(getA);
    float f1cosa = 1-cos(getA);
    PMatrix3D rotationMatrix = new PMatrix3D(
        1+f1cosa*(x2-1),    z*sina+x*y*f1cosa,      -y*sina+x*z*f1cosa,     0,
        -z*sina+x*y*f1cosa, 1+f1cosa*(y2-1),        x*sina+y*z*f1cosa,      0,
        y*sina+x*z*f1cosa,  -x*sina+y*z*f1cosa,     1+f1cosa*(z2-1),        0,
        0,                  0,                      0,                      1
    );
    return rotationMatrix;
}

// Draw
draw() {
mouseRotate();
applyMatrix(currentMatrix);
object.render();
}

我认为使用这种方法可以让我“堆叠”相对于屏幕而不是相对于对象的累积旋转。但是结果好像总是相对绘制的对象做旋转。

我没有使用相机,因为我基本上只想旋转物体本身。我实际上有点迷失了 atm 我应该旋转什么以及什么时候新应用的旋转是相对于用户的,并且之前应用的旋转是保守的。

正如您已经提到的,使用先前和当前的鼠标坐标,您可能只需在 X 轴和 Y 轴上旋转即可:

PVector cameraRotation = new PVector(0, 0);

void setup(){
  size(900, 900, P3D);
  rectMode(CENTER);
  strokeWeight(9);
  strokeJoin(MITER);
}

void draw(){
  //update "camera" rotation
  if (mousePressed){
    cameraRotation.x += -float(mouseY-pmouseY);
    cameraRotation.y += float(mouseX-pmouseX);
  }
  
  background(255);
  translate(width * 0.5, height * 0.5, 0);
  rotateX(radians(cameraRotation.x));
  rotateY(radians(cameraRotation.y));
  rect(0, 0, 300, 450);
}

您分享的 Document Paris 示例也使用了缓动。你可以看看这个minimal easing Processing example

这是上面应用了缓动的版本:

PVector cameraRotation = new PVector();
PVector cameraTargetRotation = new PVector();
float easing = 0.01;

void setup(){
  size(900, 900, P3D);
  rectMode(CENTER);
  strokeWeight(9);
  strokeJoin(MITER);
}

void draw(){
  //update "camera" rotation
  if (mousePressed){
    cameraTargetRotation.x += -float(mouseY-pmouseY);
    cameraTargetRotation.y +=  float(mouseX-pmouseX);
  }
  
  background(255);
  translate(width * 0.5, height * 0.5, 0);
  // ease rotation
  rotateX(radians(cameraRotation.x -= (cameraRotation.x - cameraTargetRotation.x) * easing));
  rotateY(radians(cameraRotation.y -= (cameraRotation.y - cameraTargetRotation.y) * easing));
  fill(255);
  rect(0, 0, 300, 450);
  fill(0);
  translate(0, 0, 3);
  rect(0, 0, 300, 450);
}

此外,还有一个名为 PeasyCam 的库可以让这一切变得更简单。

如果您确实想使用 PMatrix3D 实现您自己的版本,这里有一些可以节省您时间的提示:

  • 当你实例化时 PMatrix3D() 它是单位矩阵。如果您应用了转换并且想要 reset() 身份。
  • 如果您想围绕 PMatrix3D() 轴旋转 rotate(float angleInRadians, float axisX, float axisY, float axisZ) 覆盖应该会有所帮助。
  • 此外,您可以在没有 PMatrix3D 的情况下离开,因为 resetMatrix() 将重置全局转换矩阵,您可以直接调用 rotate(float angleInRadians, float axisX, float axisY, float axisZ)