javascript 如何用fillRect绘制一个有3个完全相同的面的等距3D立方体?

pgx2nnw8  于 2023-02-11  发布在  Java
关注(0)|答案(3)|浏览(125)

我想用fillRect创建一个等轴测3D立方体,其3个面的尺寸与下图相同:

    • 编辑:我想用fillRect**来做这个。这样做的原因是我将在立方体的3个面上绘制图像。这将非常容易做到,因为我将使用与绘制面完全相同的变换。
    • 编辑2:我没有指定要避免使用外部库**以便尽可能优化代码。我知道可以事先计算3个矩阵来绘制3个面并制作完美的等距立方体。
    • 编辑3:**如我的示例代码所示,我希望能够动态设置等距立方体的边的大小(const faceSize = 150)。

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

  • 这些面的尺寸并不都相同
  • 我不知道怎么画上面
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>
a0zr77ik

a0zr77ik1#

这里有一个解决这个问题的快速而肮脏的方法。这里太热了,我无法真正清楚地思考这个问题。(我也在矩阵数学中挣扎)我认为有两件事值得一提,每件事都对缩放操作有影响。
1.宽度和高度的完成图(和您张贴的例子图像)是不同的。
1.我认为它是填充画布1/4的未转换矩形的(相对)角之间的距离与影响缩放的完成的黄色边之间的比率。
另外,请注意,我画的是一个正方形画布。高度/2边长为黄色的一面,而我画的是一个矩形的红色和蓝色的一面。
在缩放部分中,width/4和height/4都是(width/2)/2和(height/2)/2的简写。width/2和height/2为您提供了一个填充画布1/2的矩形,其中心(正方形的中间)位于(width/2)/2,(height/2)/2 - height/4在平移部分中的含义有所不同(即使它是相同的数字)
话虽如此,这是我早些时候谈到的一类事情。

"use strict";
window.addEventListener('load', onLoaded, false);

function onLoaded(evt)
{
    let width = 147;
    let height = 171;
    let canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    document.body.appendChild(canvas);
    drawIsoDemo(canvas);
}

function drawIsoDemo(destCanvas)
{
    let ctx = destCanvas.getContext('2d');
    let width = destCanvas.width;
    let height = destCanvas.height;
    ctx.fillStyle = '#000';
    ctx.fillRect(0,0,width,height);
    
    var idMatVars = [1,0, 0,1, 0,0];
        
    // left (red) side
    let leftMat = new DOMMatrix( idMatVars );
    leftMat.translateSelf( 0, 0.25*height );
    leftMat.skewYSelf(30);
    
    ctx.save();
    ctx.transform( leftMat.a, leftMat.b, leftMat.c, leftMat.d, leftMat.e, leftMat.f);
    ctx.fillStyle = '#F00';
    ctx.fillRect(0,0,width/2,height/2);
    ctx.restore();
    
    // right (blue) side
    let rightMat = new DOMMatrix( idMatVars );
    rightMat.translateSelf( 0.5*width, 0.5*height );
    rightMat.skewYSelf(-30);
    ctx.save();
    ctx.transform( rightMat.a, rightMat.b, rightMat.c, rightMat.d, rightMat.e, rightMat.f);
    ctx.fillStyle = '#00F';
    ctx.fillRect(0,0,width/2,height/2);
    ctx.restore();
    
    // top (yellow) side
    let topMat = new DOMMatrix( idMatVars );
    let toOriginMat = new DOMMatrix( idMatVars );
    let fromOriginMat = new DOMMatrix(idMatVars);
    let rotMat = new DOMMatrix(idMatVars);
    let scaleMat = new DOMMatrix(idMatVars);

    toOriginMat.translateSelf(-height/4, -height/4);
    fromOriginMat.translateSelf(width/2,height/4);
    rotMat.rotateSelf(0,0,-45);
    scaleMat.scaleSelf(1.22,((height/2)/width)*1.22);
    
    topMat.preMultiplySelf(toOriginMat);
    topMat.preMultiplySelf(rotMat); 
    topMat.preMultiplySelf(scaleMat);   
    topMat.preMultiplySelf(fromOriginMat);  
    
    ctx.save();
    ctx.transform( topMat.a, topMat.b, topMat.c, topMat.d, topMat.e, topMat.f);
    ctx.fillStyle = '#FF0';
    ctx.fillRect(0,0,height/2,height/2);
    ctx.restore();
}
am46iovg

am46iovg2#

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

所以我们所要做的就是得到外部顶点的坐标,这很容易,因为我们可以做进一步的假设:如果你再看一遍这个形状,你会注意到立方体每一边的长度,似乎就是圆的半径.
借助一点三角函数和一个以60度递增的for循环,我们可以把calculate和所有顶点放入一个数组中,最后把这些顶点连接起来画出立方体,下面是一个例子:

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;
}
<canvas id="canvas" width="400" height="300"></canvas>

编辑

你想要实现的并不是那么容易,因为CanvasRenderingContext2D API实际上 * 不 * 提供倾斜/剪切变换。然而,在第三方库的帮助下,我们能够以正交的方式变换三个边。它被称为perspective.js
我们仍然需要计算外部顶点,但不是使用moveTo/lineTo命令,而是将坐标转发到perspective.js,以实际执行一些源图像的透视变形。
下面是另一个例子:
一个一个三个一个一个一个一个一个四个一个一个一个一个一个五个一个

x4shl7ld

x4shl7ld3#

我使用了 *@enhzflep * 代码的很大一部分,并对其进行了修改,以便立方体的宽度可以动态更改。
所有的代码看起来在数学上都是正确的,我只是对scaleSelf的参数值1.22有一点疑问。为什么要选择这个精确的值?

下面是代码:

window.addEventListener('load', onLoad, false);

const canvas = document.createElement('canvas');

function onLoad() {
    //canvas.width = cubeWidth;
    //canvas.height = faceSize * 2;
    canvas.width = 400;
    canvas.height = 400;
    document.body.appendChild(canvas);
    drawCube(canvas);
}

function drawCube() {

    const scale = Math.abs(Math.sin(Date.now() / 1000) * canvas.width / 200); // scale effect
    const faceSize = 100 * scale;
    const radians = 30 * Math.PI / 180;
    const cubeWidth = faceSize * Math.cos(radians) * 2;
    const centerPosition = {
        x: canvas.width / 2,
        y: canvas.height / 2
    };

    const ctx = canvas.getContext('2d');
    ctx.save();
    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    const defaultMat = [1, 0, 0, 1, 0, 0];

    // Left (red) side
    const leftMat = new DOMMatrix(defaultMat);
    leftMat.translateSelf(centerPosition.x - cubeWidth / 2, centerPosition.y - faceSize / 2);
    leftMat.skewYSelf(30);
    ctx.setTransform(leftMat);
    ctx.fillStyle = '#F00';
    ctx.fillRect(0, 0, cubeWidth / 2, faceSize);

    // Right (blue) side
    const rightMat = new DOMMatrix(defaultMat);
    rightMat.translateSelf(centerPosition.x, centerPosition.y);
    rightMat.skewYSelf(-30);
    ctx.setTransform(rightMat);
    ctx.fillStyle = '#00F';
    ctx.fillRect(0, 0, cubeWidth / 2, faceSize);

    // Top (yellow) side
    const topMat = new DOMMatrix(defaultMat);
    const toOriginMat = new DOMMatrix(defaultMat);
    const fromOriginMat = new DOMMatrix(defaultMat);
    const rotMat = new DOMMatrix(defaultMat);
    const scaleMat = new DOMMatrix(defaultMat);

    toOriginMat.translateSelf(-faceSize / 2, -faceSize / 2);
    fromOriginMat.translateSelf(centerPosition.x, centerPosition.y - faceSize / 2);
    rotMat.rotateSelf(0, 0, -45);
    scaleMat.scaleSelf(1.22, (faceSize / cubeWidth) * 1.22);

    topMat.preMultiplySelf(toOriginMat);
    topMat.preMultiplySelf(rotMat);
    topMat.preMultiplySelf(scaleMat);
    topMat.preMultiplySelf(fromOriginMat);

    ctx.setTransform(topMat);
    ctx.fillStyle = '#FF0';
    ctx.fillRect(0, 0, faceSize, faceSize);
    ctx.restore();

    requestAnimationFrame(drawCube);
}

相关问题