使用转换API的3d幻觉效果

时间:2019-03-10 08:59:25

标签: javascript animation math canvas formula

我正在研究一个简单的太空飞船2d游戏,我正在考虑通过利用transform api来创造漂亮的3d幻觉效果。通过实验,我最终调整了a(水平缩放)和d(垂直缩放)。

因此,默认情况下(不进行转换),我们可以使用ctx.transform(1, 0, 0, 1, 0, 0);

我当前的设置是这个ctx.transform(1.72, 0, 0, 0.65, 0, 0);

通过这种设置,飞船看起来不错,但仅在某些角度下才可以。我在想是否有一种方法可以使用适当的数学方法来应用公式来动态调整这些值(可能基于船旋转的角度)。不幸的是,我的数学知识非常差,所以我要求更多有经验的成员帮忙弄清楚它。

下面是小的原型(通过单击单选按钮,您可以在默认值和我的硬编码值之间切换):

var mode = "3d";
var toggle = function(ev, item) {
  mode = item.value;
};

var tools = new function() {
  this.rad = a => (Math.PI / 180) * a;
  this.deg = rad => (rad * 180) / Math.PI;
  this.distance = p =>
    Math.sqrt((p.x - p.dx) * (p.x - p.dx) + (p.y - p.dy) * (p.y - p.dy));
  this.rftv = p => Math.atan2(p.dy - p.y, p.dx - p.x);
  this.pfa = function(l, x, y, a) {
    return {
      x: Math.cos(this.rad(a)) * l + x,
      y: Math.sin(this.rad(a)) * l + y
    };
  };
}();

let design = {
  c: [
    { size: 0.7166666666666667, deg: -90 },
    { size: 0.5060742150229658, deg: -107.24145939893998 },
    { size: 0.42196629670573876, deg: -99.09027692082233 },
    { size: 0.08975274678557507, deg: -158.19859051364818 },
    { size: 0.08975274678557507, deg: -21.80140948635181 },
    { size: 0.42196629670573876, deg: -80.90972307917767 },
    { size: 0.5060742150229658, deg: -72.75854060106003 }
  ],
  l1: [
    { size: 0.4552166761249221, deg: -113.74949449286676 },
    { size: 0.3901566636906542, deg: -109.98310652189998 },
    { size: 0.18408935028645435, deg: -174.8055710922652 },
    { size: 0.6324555320336759, deg: 161.565051177078 }
  ],
  r1: [
    { size: 0.3901566636906542, deg: -70.01689347810003 },
    { size: 0.4552166761249221, deg: -66.25050550713325 },
    { size: 0.6324555320336759, deg: 18.43494882292201 },
    { size: 0.18408935028645435, deg: -5.194428907734806 }
  ],
  l2: [
    { size: 0.2608745973749754, deg: 153.434948822922 },
    { size: 0.6262764742685312, deg: 154.79887635452494 },
    { size: 0.6616477747093069, deg: 130.91438322002512 }
  ],
  r2: [
    { size: 0.2608745973749754, deg: 26.56505117707799 },
    { size: 0.6262764742685312, deg: 25.20112364547507 },
    { size: 0.6616477747093069, deg: 49.08561677997487 }
  ]
};

let circle = (x, y, r, fs, ss) => {
  ctx.save();
  ctx.beginPath();
  ctx.arc(x, y, r, 0, Math.PI * 2);
  if (fs !== false) {
    ctx.fillStyle = fs;
    ctx.fill();
  }
  if (ss !== false) {
    ctx.lineWidth = 1;
    ctx.strokeStyle = ss;
    ctx.stroke();
  }
  ctx.restore();
};

var transform = function(zAxis, tilt, scale, x, y) {
	var cs = Math.cos(zAxis), sn = Math.sin(zAxis);
	var h = Math.cos(tilt);
	var a = scale*cs, b = -scale*sn, c = x;
	var d = h*scale*sn, e = h*scale*cs, f = y;
  return { a, d, b, e, c, f };
};

let ship = (x, y, size, a, fs) => {
  ctx.save();
  ctx.beginPath();

  ctx.translate(x, y);
  if (mode === "2d") {
    ctx.transform(1, 0, 0, 1, 0, 0);
  }
  if (mode === "3d") {
    var { a, d, b, e, c, f } = transform(tools.rad(a), 45, 1, x, y);
    ctx.setTransform(a, d, b, e, c, f);
  }
  ctx.translate(-x, -y);

  for (let type in design) {
    for (let i = 0; i < design[type].length; i++) {
      let c = design[type][i],
        p = tools.pfa(size * c.size, x, y, c.deg + a + 90);
      if (i === 0) {
        ctx.moveTo(p.x, p.y);
      } else {
        ctx.lineTo(p.x, p.y);
      }
    }
    if (design[type].length > 0) {
      let c = design[type][0],
        p = tools.pfa(size * c.size, x, y, c.deg + a + 90);
      ctx.lineTo(p.x, p.y);
    }
  }

  ctx.fillStyle = fs;
  ctx.fill();
  ctx.restore();

  circle(x, y, size, false, "blue");
};

let cvs = document.createElement("canvas"),
  ctx = cvs.getContext("2d"),
  w = (cvs.width = 400),
  h = (cvs.height = 400),
  cx = w / 2,
  cy = h / 2;

let points = [
  { x: cx - 40, y: 3 },
  { x: cx + 40, y: h - 3 },
  { x: 3, y: cy + 40 },
  { x: w - 3, y: cy - 40 }
];

let shipData = {
  x: cx,
  y: cy,
  r: 40,
  a: 0,
  c: 0,
  dx: points[0].x,
  dy: points[0].y,
  run: function() {
    let d = tools.distance(this);
    if (d < 1) {
      this.c += 1;
      if (this.c > points.length - 1) {
        this.c = 0;
      }
      this.dx = points[this.c].x;
      this.dy = points[this.c].y;
    }
    let rad = tools.rftv(this);
    this.a = tools.deg(rad);
    this.x += Math.cos(rad);
    this.y += Math.sin(rad);
  }
};

let render = () => {
  ctx.clearRect(0, 0, w, h);
  ctx.fillStyle = "#ccc";
  ctx.fillRect(0, 0, w, h);

  /* debug */ circle(points[0].x, points[0].y, 3, "red", false);
  /* debug */ circle(points[1].x, points[1].y, 3, "red", false);
  /* debug */ circle(points[2].x, points[2].y, 3, "red", false);
  /* debug */ circle(points[3].x, points[3].y, 3, "red", false);

  ship(shipData.x, shipData.y, shipData.r, shipData.a, "blue");
  shipData.run();

  requestAnimationFrame(render);
};

document.body.appendChild(cvs);
render();
<div>
	2d <input type="radio" onclick="toggle(event, this);" name="display" value="2d">
	3d <input type="radio" onclick="toggle(event, this);" name="display" value="3d" checked>
</div>

---编辑

我根据此topic进行了修改,答案中有人提出了用于计算矩阵的公式,但是使用我的设置似乎无效。

我是这样实现的:

var transform = function(angle1, angle2, size1, size2) {
  var cs = Math.cos(angle1), sn = Math.sin(angle1);
  var h = Math.cos(angle2);
  var a = size1*cs, b = -size1*sn, c = size2;
  var d = h*size1*sn, e = h*size1*cs, f = size2;
  return { a, d, b, e, c, f };
};

if (mode === "3d") {
    var { a, d, b, e, c, f } = transform(a, a, size * 0.5, size);
    ctx.setTransform(a, d, b, e, c, f);
}

---编辑2

借助评论,我假设以下内容:

var transform = function(zAxis, tilt, scale, x, y) {
    var cs = Math.cos(zAxis), sn = Math.sin(zAxis);
    var h = Math.cos(tilt);
    var a = scale*cs, b = -scale*sn, c = x;
    var d = h*scale*sn, e = h*scale*cs, f = y;
    return { a, d, b, e, c, f };
};

if(mode === '3d') {
    var {a, d, b, e, c, f} = transform(tools.rad(a), 45, 1, x, y);
    ctx.setTransform(a, d, b, e, c, f);
}

所以我想scale 1应该还可以,并且由于我的x, y是船的中心,所以我可以使用它们。难题的最后一部分将是第二个参数global tilt,我确实将其设置为45 deg,它看起来还不错,但是我不知道它的100%正确,所以我更新了片段供他人查看。

1 个答案:

答案 0 :(得分:1)

您可以使用画布轻松渲染等电(非透视)图形,并且CSS也可以使用相同的转换。矩阵系数取决于倾斜角(通常在等距游戏中固定)和绕Z轴(高度)的旋转。

有关完整的公式,您可以看到以下答案:https://stackoverflow.com/a/5186153/320726