将立方体旋转为等距
Rotate a cube to be isometric
我正在学习 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;
}
<canvas id="stage" height='500px' width='500px'></canvas>
编辑:我应该附上一张图片来进一步解释我想要实现的目标。我有一张等轴测图 (45°,30°) 的房间图片,我用 canvas 覆盖它,以便我可以在上面绘制立方体。如您所见,它有点偏离,我认为这是两个复合旋转的效果,因为每个函数都会改变原始节点坐标。
你想要投影而不是旋转
您的问题是您正在尝试应用投影,但使用的是变换矩阵。
变换矩阵将使盒子保持其原始形状,每个轴与其他轴成 90 度。
您希望一个轴为 45 度,另一个为 30 度。仅靠轮换无法做到这一点。
投影矩阵
基本的 3 x 4 矩阵表示 4 个 3D 向量。这些向量是 3D 中 x、y、z 轴的方向和比例 space,第 4 个向量是原点。
投影矩阵删除了将坐标转换为 2D 的 z 部分 space。每个轴的z部分为0.
由于等距投影是平行的,我们可以创建一个矩阵来设置 canvas 上的 2D 轴方向。
轴
x 轴 45 度
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
上面代码的一个例子
我在代码片段中列出了一个非常基本的矩阵以供参考。
代码段使用您的近似布局创建 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);
<canvas id="canvas" height='200' width='500'></canvas>
我认为问题可能是围绕房间的 x 轴旋转不是 30°。在等距图像中,立方体的边与水平面之间通常呈 30° 角。但是为了得到这个水平角度,绕x轴旋转大约35°(atan(1/sqrt(2))
)。参见 overview in the Wikipedia article.
话虽如此,有时在计算机图形中,立方体的边与水平线之间的角度约为 27° (atan(0.5)
),因为这会在计算机屏幕上产生更整洁的光栅线。那样的话,绕x轴旋转就是30°。查看 this article 了解更多关于不同类型投影的信息。
我正在学习 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;
}
<canvas id="stage" height='500px' width='500px'></canvas>
编辑:我应该附上一张图片来进一步解释我想要实现的目标。我有一张等轴测图 (45°,30°) 的房间图片,我用 canvas 覆盖它,以便我可以在上面绘制立方体。如您所见,它有点偏离,我认为这是两个复合旋转的效果,因为每个函数都会改变原始节点坐标。
你想要投影而不是旋转
您的问题是您正在尝试应用投影,但使用的是变换矩阵。
变换矩阵将使盒子保持其原始形状,每个轴与其他轴成 90 度。
您希望一个轴为 45 度,另一个为 30 度。仅靠轮换无法做到这一点。
投影矩阵
基本的 3 x 4 矩阵表示 4 个 3D 向量。这些向量是 3D 中 x、y、z 轴的方向和比例 space,第 4 个向量是原点。
投影矩阵删除了将坐标转换为 2D 的 z 部分 space。每个轴的z部分为0.
由于等距投影是平行的,我们可以创建一个矩阵来设置 canvas 上的 2D 轴方向。
轴
x 轴 45 度
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
上面代码的一个例子
我在代码片段中列出了一个非常基本的矩阵以供参考。
代码段使用您的近似布局创建 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);
<canvas id="canvas" height='200' width='500'></canvas>
我认为问题可能是围绕房间的 x 轴旋转不是 30°。在等距图像中,立方体的边与水平面之间通常呈 30° 角。但是为了得到这个水平角度,绕x轴旋转大约35°(atan(1/sqrt(2))
)。参见 overview in the Wikipedia article.
话虽如此,有时在计算机图形中,立方体的边与水平线之间的角度约为 27° (atan(0.5)
),因为这会在计算机屏幕上产生更整洁的光栅线。那样的话,绕x轴旋转就是30°。查看 this article 了解更多关于不同类型投影的信息。