遗憾的是,除了使用 3D 方法之外,没有合适的方法。但幸运的是,它并没有那么复杂。
下面将生成一个可按 X 轴旋转的网格(如您的图片中所示),因此我们只需要关注该轴即可。
要了解发生了什么:我们在笛卡尔坐标空间中定义网格。说我们将点定义为向量而不是绝对坐标的花哨的词。也就是说,一个网格单元可以从 0,0 到 1,1 而不是例如 10,20 到 45, 45 只是为了取一些数字。
在投影阶段,我们将这些笛卡尔坐标投影到我们的屏幕坐标中。
结果将是这样的:
ONLINE DEMO
好的,让我们深入研究一下 - 首先我们设置一些投影等所需的变量:
fov = 512, /// Field of view kind of the lense, smaller values = spheric
viewDist = 22, /// view distance, higher values = further away
w = ez.width / 2, /// center of screen
h = ez.height / 2,
angle = -27, /// grid angle
i, p1, p2, /// counter and two points (corners)
grid = 10; /// grid size in Cartesian
要调整网格,我们不调整循环(见下文),而是更改fov
和viewDist
以及修改grid
以增加或减少单元格的数量。
假设您想要一个更极端的视图 - 通过设置fov
为 128 和5,您将使用相同的和viewDist
获得此结果:grid
angle
执行所有数学运算的“魔术”函数如下:
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180; /// convert angle into radians
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca; /// convert y value as we are rotating
rz = y * sa; /// only around x. Z will also change
/// Project the new coords into screen coords
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
就是这样。值得一提的是,新的 Y 和 Z 的组合使顶部的线条更小(在这个角度)。
现在我们可以像这样在笛卡尔空间中创建一个网格,并将这些点直接旋转到屏幕坐标空间中:
/// create vertical lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}
还要注意位置 0,0 是屏幕的中心。这就是为什么我们使用负值从左侧或上方退出的原因。可以看到两条中心线是直线。
这就是它的全部。要为单元格着色,您只需选择笛卡尔坐标,然后通过调用对其进行转换rotateX()
,您将获得角所需的坐标。
例如 - 随机选取一个单元格编号(X 轴和 Y 轴上的 -10 到 10 之间):
c1 = rotateX(cx, cy); /// upper left corner
c2 = rotateX(cx + 1, cy); /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1); /// bottom left corner
/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();
/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();
一个动画版本,可以帮助了解发生了什么。