【问题标题】:How to draw an isometric 3D cube with 3 perfectly identical faces with fillRect?如何使用 fillRect 绘制具有 3 个完全相同面的等距 3D 立方体?
【发布时间】:2023-02-09 22:57:21
【问题描述】:

我想创建一个等轴测 3D 立方体带填充矩形其 3 个面具有与下图相同的尺寸:

我有一个代码的开头,但我有两个问题:

  • 面的尺寸不尽相同
  • 我不知道怎么画顶面

const faceSize = 150;
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;

// Top Face (not big enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.scale(1, .5);
ctx.rotate(-45 * Math.PI / 180);
ctx.fillStyle = 'yellow';
ctx.fillRect(0, -faceSize, faceSize, faceSize);
ctx.restore();
  
// Left Face (not high enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.transform(1, .5, 0, 1, 0, 0);
ctx.fillStyle = 'red';
ctx.fillRect(-faceSize, 0, faceSize, faceSize);
ctx.restore();

// Right Face (not high enough)
ctx.save();
ctx.translate(centerX, centerY);
ctx.transform(1, -.5, 0, 1, 0, 0);
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, faceSize, faceSize);
ctx.restore();
<canvas width="400" height="400"></canvas>

【问题讨论】:

  • 所以本质上,您想用 3 种不同的颜色填充六边形。在考虑屏幕空间和模型空间之间的区别(使用 openGL 类比)之前,我发现很难绘制六边形的重复图案。一旦我意识到我们也可以在 [0..1] 空间中定义它的坐标,并且它们的宽度是高度的 86%,它就变得显而易见了。所有坐标均为 0、0.25、0.5、0.75 或 1.0。原点在左上角,从顶部顶点开始,逆时针移动,顶点为 <0.5,0>, <0,0.25>, <0,0.75>, <0.5,1>, <1,0.75> , <1,0.25>
  • [续] 此坐标系会将中心顶点置于 <0.5,0.5>。因此,要绘制一个 171 像素高的六边形,您可以将所有 x 和 y 对缩放 171。然后您可以将所有 y 值缩放 0.86。这将为您提供顶点坐标。从那里开始,填充 3 个 4 边形是一件简单的事情。其中 3 个顶点来自轮廓,第 4 个来自中心点。
  • 我真的很想用 canvas 2D 和 fillRect 制作立方体。这样做的原因是之后我将在立方体的 3 个面上绘制图像。这将非常容易做到,因为我将使用与绘制面部完全相同的变换。
  • 哦……我明白了。可能值得针对问题进行编辑。好吧,走着瞧。我想您需要将以下转换列表相乘:(1) translateX -1/2 宽度,旋转 -45deg,scaleX 200%,translateX +1/2 宽度。我懒得为每个操作设置一个矩阵,然后将它们相乘以得出 a、b、c、d、e、f 值,抱歉!留给读者的练习。 (如果读者认为我提出的操作清单听起来理智合理)

标签: javascript canvas html5-canvas


【解决方案1】:

如果我们在等距立方体上叠加一个圆,我们可以看到外部顶点的间距相等。事实上它总是 60°,这并不奇怪,因为它是六边形。

所以我们所要做的就是获取外部顶点的坐标。这很容易,因为我们可以做一个进一步的假设:如果你再看一下这个形状,你会注意到立方体每条边的长度似乎就是圆的半径。

借助一点三角函数和一个递增 60 度的 for 循环,我们可以计算并将所有这些顶点放入一个数组中,最后连接这些顶点以绘制立方体。 这是一个例子:

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");

function drawCube(x, y, sideLength) {
  let vertices = [new Point(x, y)];
  for (let a = 0; a < 6; a++) {
    vertices.push(new Point(x + Math.cos(((a * 60) - 30) * Math.PI / 180) * sideLength, y + Math.sin(((a * 60) - 30) * Math.PI / 180) * sideLength));
  }
  ctx.fillStyle = "#ffffff";
  ctx.beginPath();
  ctx.moveTo(vertices[0].x, vertices[0].y);
  ctx.lineTo(vertices[5].x, vertices[5].y);
  ctx.lineTo(vertices[6].x, vertices[6].y);
  ctx.lineTo(vertices[1].x, vertices[1].y);
  ctx.lineTo(vertices[0].x, vertices[0].y);
  ctx.fill();

  ctx.fillStyle = "#a0a0a0";
  ctx.beginPath();
  ctx.moveTo(vertices[0].x, vertices[0].y);
  ctx.lineTo(vertices[1].x, vertices[1].y);
  ctx.lineTo(vertices[2].x, vertices[2].y);
  ctx.lineTo(vertices[3].x, vertices[3].y);
  ctx.lineTo(vertices[0].x, vertices[0].y);
  ctx.fill();

  ctx.fillStyle = "#efefef";
  ctx.beginPath();
  ctx.moveTo(vertices[0].x, vertices[0].y);
  ctx.lineTo(vertices[3].x, vertices[3].y);
  ctx.lineTo(vertices[4].x, vertices[4].y);
  ctx.lineTo(vertices[5].x, vertices[5].y);
  ctx.lineTo(vertices[0].x, vertices[0].y);
  ctx.fill();
}

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

drawCube(200, 150, 85);
canvas {
  background: #401fc1;
}
&lt;canvas id="canvas" width="400" height="300"&gt;&lt;/canvas&gt;

【讨论】:

  • 我已经指定我想用 fillRect 来完成它。这样做的原因是之后我将在立方体的 3 个面上绘制图像。这将非常容易做到,因为我将使用与绘制面部完全相同的变换。
  • @Dady - 我忘了说,我使用自己的矢量/矩阵库,但我认为您应该能够使用 SVGMatrix 从我建议的 4 个变换中计算出最终变换。虽然我看到它们已被弃用,但您应该改用 DOMMatrix。此处备注:developer.mozilla.org/en-US/docs/Web/API/DOMMatrix
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-19
  • 1970-01-01
  • 1970-01-01
  • 2015-01-09
  • 2021-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多