【问题标题】:Rotate a cube to be isometric将立方体旋转为等距
【发布时间】:2017-11-15 03:13:53
【问题描述】:

我正在关注this rotating cube 教程,我正在尝试将立方体旋转到等距视角(45 度、30 度)。

我认为,问题在于 rotateY 和 rotateX 函数改变了原始值,使得立方体中间的两个红点(视觉上)不重叠。 (如果这有任何意义)

我怎样才能同时在它的 X 和 Y 轴上旋转立方体,这样功能就不会相互影响?

const canvas = document.getElementById('stage');
    canvas.width = canvas.parentElement.clientWidth
    canvas.height = canvas.parentElement.clientHeight
    const context = canvas.getContext('2d');
    context.translate(200,200)

    var node0 = [-100, -100, -100];
    var node1 = [-100, -100,  100];
    var node2 = [-100,  100, -100];
    var node3 = [-100,  100,  100];
    var node4 = [ 100, -100, -100];
    var node5 = [ 100, -100,  100];
    var node6 = [ 100,  100, -100];
    var node7 = [ 100,  100,  100];
    var nodes = [node0, node1, node2, node3, node4, node5, node6, node7];

    var edge0  = [0, 1];
    var edge1  = [1, 3];
    var edge2  = [3, 2];
    var edge3  = [2, 0];
    var edge4  = [4, 5];
    var edge5  = [5, 7];
    var edge6  = [7, 6];
    var edge7  = [6, 4];
    var edge8  = [0, 4];
    var edge9  = [1, 5];
    var edge10 = [2, 6];
    var edge11 = [3, 7];
    var edges = [edge0, edge1, edge2, edge3, edge4, edge5, edge6, edge7, edge8, edge9, edge10, edge11];

    var draw = function(){

      for (var e=0; e<edges.length; e++){
        var n0 = edges[e][0]
        var n1 = edges[e][1]
        var node0 = nodes[n0];
        var node1 = nodes[n1];
        
        context.beginPath();
        context.moveTo(node0[0],node0[1]);
        context.lineTo(node1[0],node1[1]);
        context.stroke();
      }

      //draw nodes
      for (var n=0; n<nodes.length; n++){
        var node = nodes[n];
        context.beginPath();
        context.arc(node[0], node[1], 3, 0, 2 * Math.PI, false);
        context.fillStyle = 'red';
        context.fill();
      }
    }


    var rotateZ3D = function(theta){
      var sin_t = Math.sin(theta);
      var cos_t = Math.cos(theta);
      for (var n=0; n< nodes.length; n++){
        var node = nodes[n];
        var x = node[0];
        var y = node[1];
        node[0] = x * cos_t - y * sin_t;
        node[1] = y * cos_t + x * sin_t;
      };
    };

    var rotateY3D = function(theta){
      var sin_t = Math.sin(theta);
      var cos_t = Math.cos(theta);

      for (var n=0; n<nodes.length; n++){
        var node = nodes[n];
        var x = node[0];
        var z = node[2];
        node[0] = x * cos_t - z * sin_t;
        node[2] = z * cos_t + x * sin_t;
      }
    };

    var rotateX3D = function(theta){
      var sin_t = Math.sin(theta);
      var cos_t = Math.cos(theta);

      for (var n = 0; n< nodes.length; n++){
        var node = nodes[n];
        var y = node[1];
        var z = node[2];
        
        node[1] = y * cos_t - z * sin_t;
        node[2] = z * cos_t + y * sin_t;
      }
    }

    rotateY3D(Math.PI/4);
    rotateX3D(Math.PI/6);


    draw();
#stage {
  background-color: cyan;
 }
&lt;canvas id="stage" height='500px' width='500px'&gt;&lt;/canvas&gt;

编辑:我应该附上一张图片来进一步解释我想要实现的目标。我有一张等距(45°,30°)的房间图片,我用画布覆盖它,这样我就可以在上面绘制立方体。正如你所看到的,它有点偏离,我认为它是两个复合旋转的效果,因为每个函数都会改变原始节点坐标。

【问题讨论】:

  • 我通过旋转 X 得到了想要的效果:(Math.atan( - 1 / Math.sqrt( 2 ) ) ) 而不是 (Math.PI/6)。我不明白为什么。
  • 您希望边缘与水平线之间的角度为 30 度,但这并不意味着您旋转 30 度。维基百科在这里解释:en.wikipedia.org/wiki/Isometric_projection#Rotation_angles

标签: javascript canvas processing.js


【解决方案1】:

你想要投影而不是旋转

您的问题是您正在尝试应用投影,但使用了转换矩阵来执行此操作。

变换矩阵将使盒子保持其原始形状,每个轴与其他轴成 90 度。

您希望一个轴为 45 度,另一个轴为 30 度。仅靠旋转无法做到这一点。

投影矩阵

基本的 3 x 4 矩阵表示 4 个 3D 向量。这些向量是 3D 空间中 x,y,z 轴的方向和尺度,第 4 个向量是原点。

投影矩阵移除了将坐标转换为二维空间的 z 部分。每个轴的z部分为0。

由于等轴测投影是平行的,我们可以创建一个矩阵来设置画布上的 2D 轴方向。

45 度的 xAxis

const xAxis = Math.PI * ( 1 /  4);
iso.x.set(Math.cos(xAxis), Math.sin(xAxis), 0);

120 度的 y 轴

const yAxis = Math.PI * ( 4 / 6);
iso.y.set(Math.cos(yAxis), Math.sin(yAxis), 0);

还有页面上方的z轴

iso.z.set(0,-1,0);

转型

然后我们只需将每个顶点坐标乘以相应的轴

// m is the matrix (iso)
// a is vertex in
// b is vertex out
// m.o is origin (not used in this example
b.x = a.x * m.x.x + a.y * m.y.x + a.z * m.z.x + m.o.x;
b.y = a.x * m.x.y + a.y * m.y.y + a.z * m.z.y + m.o.y;
b.z = a.x * m.x.z + a.y * m.y.z + a.z * m.z.z + m.o.z;
//    ^^^^^^^^^^^   ^^^^^^^^^^^   ^^^^^^^^^^^  
//    move x dist   move y dist   move z dist
//    along x axis  along y axis  along y axis
//     45deg          120deg        Up -90deg

以上代码示例

我在 sn-p 中列出了一个非常基本的矩阵供参考。

sn-p 使用您的近似布局创建 3D 对象。

转换需要第二个对象作为结果

我还添加了一个projectIso,它采用 x、y、z 轴的方向和 x、y、z 轴的比例,并创建一个如上所述的投影矩阵。

这样就完成了上面的操作

const mat = Mat().projectIso(
    Math.PI * ( 1 / 4), 
    Math.PI * ( 4 / 6),
    Math.PI * ( 3 / 2)  // up
); // scales default to 1

const ctx = canvas.getContext('2d');

var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center 
var ch = h / 2;

const V = (x,y,z) => ({x,y,z,set(x,y,z){this.x = x;this.y = y; this.z = z}});
const Mat = () => ( {
   x : V(1,0,0),
   y : V(0,1,0),
   z : V(0,0,1),
   o : V(0,0,0), // origin
   ident(){
      const m = this;
      m.x.set(1,0,0);
      m.y.set(0,1,0);
      m.z.set(0,0,1);
      m.o.set(0,0,0);
      return m;
   },
   rotX(r) {
      const m = this.ident();      
      m.y.set(0, Math.cos(r), Math.sin(r));
      m.z.set(0, -Math.sin(r), Math.cos(r));
      return m;      
   },
   rotY(r) {
      const m = this.ident();      
      m.x.set(Math.cos(r), 0, Math.sin(r));
      m.z.set(-Math.sin(r), 0, Math.cos(r));
      return m;      
   },      
   rotZ(r) {
      const m = this.ident();      
      m.x.set(Math.cos(r), Math.sin(r), 0);
      m.y.set(-Math.sin(r), Math.cos(r), 0);
      return m;      
   },    
   projectIso(xAxis, yAxis, zAxis, xScale = 1, yScale = 1, zScale = 1) {
      const m = this.ident();      
      iso.x.set(Math.cos(xAxis) * xScale, Math.sin(xAxis) * xScale, 0);
      iso.y.set(Math.cos(yAxis) * yScale, Math.sin(yAxis) * yScale, 0);
      iso.z.set(Math.cos(zAxis) * zScale, Math.sin(zAxis) * zScale, 0);
      return m;
   },
   transform(obj, result){
      const m = this;
      const na = obj.nodes;
      const nb = result.nodes;
      var i = 0;
      while(i < na.length){
         const a = na[i];
         const b = nb[i++];
         b.x = a.x * m.x.x + a.y * m.y.x + a.z * m.z.x + m.o.x;
         b.y = a.x * m.x.y + a.y * m.y.y + a.z * m.z.y + m.o.y;
         b.z = a.x * m.x.z + a.y * m.y.z + a.z * m.z.z + m.o.z;
      }
      return result;
   }
});

// create a box
const Box = (size = 35) =>( {
  nodes: [
    V(-size, -size, -size),
    V(-size, -size, size),
    V(-size, size, -size),
    V(-size, size, size),
    V(size, -size, -size),
    V(size, -size, size),
    V(size, size, -size),
    V(size, size, size),
  ],
  edges: [[0, 1],[1, 3],[3, 2],[2, 0],[4, 5],[5, 7],[7, 6],[6, 4],[0, 4],[1, 5],[2, 6],[3, 7]],
});

// draws a obj that has nodes, and edges

function draw(obj) {
    ctx.fillStyle = 'red';
  const edges =  obj.edges;
  const nodes =  obj.nodes;
  var i = 0;
  ctx.beginPath();
  while(i < edges.length){
    var edge = edges[i++];
    ctx.moveTo(nodes[edge[0]].x, nodes[edge[0]].y);
    ctx.lineTo(nodes[edge[1]].x, nodes[edge[1]].y);
    
  }
  ctx.stroke();    
  i = 0;
  ctx.beginPath();
  while(i < nodes.length){
    const x = nodes[i].x;
    const y = nodes[i++].y;
    ctx.moveTo(x+3,y);
    ctx.arc(x,y, 3, 0, 2 * Math.PI, false);
  }
  ctx.fill();
}

// create boxes (box1 is the projected result)
var box = Box();
var box1 = Box();
var box2 = Box();

// create the projection matrix
var iso = Mat();
// angles for X, and Y axis
const xAxis = Math.PI * ( 1 / 4);
const yAxis = Math.PI * ( 4 / 6);
iso.x.set(Math.cos(xAxis), Math.sin(xAxis),0);
iso.y.set(Math.cos(yAxis), Math.sin(yAxis), 0);
// the direction of Z
iso.z.set(0, -1, 0);

// center rendering
    
ctx.setTransform(1,0,0,1,cw* 0.5,ch);

// transform and render
draw(iso.transform(box,box1));

iso.projectIso(Math.PI * ( 1 / 6), Math.PI * ( 5 / 6), -Math.PI * ( 1 / 2))
ctx.setTransform(1,0,0,1,cw* 1,ch);
draw(iso.transform(box,box1));

iso.rotY(Math.PI / 4);
iso.transform(box,box1);
iso.rotX(Math.atan(1/Math.SQRT2));
iso.transform(box1,box2);
ctx.setTransform(1,0,0,1,cw* 1.5,ch);
draw(box2);
&lt;canvas id="canvas" height='200' width='500'&gt;&lt;/canvas&gt;

【讨论】:

  • 信息量很大,我将通过它来了解我们方法之间的差异。我更新了最初的问题,因为我没有正确传达我试图达到的结果。我正在尝试将立方体与水平 45 度和垂直 30 度的背景(包括图片)匹配。
  • @Ashbury 我的代码中有两个拼写错误,影响了projectIso 函数。修复了它,演示在右边有你想要渲染的投影,并在 sn-p 的最后 3 行完成。 X 是 30 度(其中 0 度是从左到右的水平) Y 是 150 度,Z 仍然是 -90 度
【解决方案2】:

我认为问题可能是房间围绕 x 轴的旋转不是 30°。在等轴测图像中,立方体的边与水平面之间通常存在 30° 的角度。但是为了得到这个水平角度,绕 x 轴的旋转应该是大约 35° (atan(1/sqrt(2)))。请参阅overview in the Wikipedia article

话虽如此,有时在计算机图形中,立方体的边与水平面之间的角度约为 27° (atan(0.5)),因为这会在计算机屏幕上产生更整齐的光栅线。在这种情况下,围绕 x 轴的旋转 30°。查看this article,了解有关不同类型投影的更多信息。

【讨论】:

  • 如果您测量给定图像的像素大小,则顶角的三角形为 266 像素 x 154 像素,对角​​线偏离 306 像素,与墙壁高度相匹配。 x 和 y 轴的角度为asin(154 / 266) 30deg 和 150deg。这有一个立方体的近角和远角彼此重叠,并占据一个六边形,内角为 120 度,总高度是墙高的两倍 306 * 2 = 616px(这种类型中常见的一个像素偏移投影,测量高度为615px)
  • 所以如果图片中x轴的角度是30度,需要将立方体旋转atan(1/sqrt(2))。
  • 抱歉,“通过 atan(1/sqrt(2)) 旋转立方体”是没有意义的 立方体是 3D 的,旋转需要一个旋转轴。但即便如此,它也不会起作用,因为它不需要旋转。所需投影的 xAxis 为 30 度,y 轴为 150 度,z 轴为 -90 度。盒子被扭曲以适应投影的要求。看我的sn-p右边的框是OP所追求的投影。
  • 正如我在回答中所说,旋转是关于 x 轴的。这篇维基百科文章解释了为什么旋转是 ~35 度而不是 30 度:en.wikipedia.org/wiki/Isometric_projection#Rotation_angles
  • 您不要将其用于计算机图形,它会将投影的大小减少约 80%,并由于像素查找(双线性或最近)而导致伪影,并且只会浪费您使用的内存64% 的像素。我概述的方法保持 1 到 1 像素的比例。如果您映射一个 100 乘 100 的图像,则 3 个可见边的总面积为 30000 像素。 (见我的回答,右边第三个投影是通过wiki方法)
猜你喜欢
  • 2010-11-08
  • 2013-07-17
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 2021-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多