3

我正在玩 HTML5 画布,我正在尝试实现一种使用平移、缩放和旋转在画布上移动图像的方法。

我已经使用 setTransform 进行了翻译和缩放:

canvas.getContext('2d').setTransform(a,b,c,d,e,f)

这很方便,因为它丢弃了以前应用的变换,然后应用新的变换,因此在缩放等时不需要记住以前的状态。

在 W3 学校中,第二个和第三个参数是skewYskewX,我最初假设是旋转 x 和 y。然而,在应用将一些值传递给这些参数的变换之后,它似乎没有旋转 - 它使画布倾斜!(我知道很奇怪:-D)。

谁能告诉我为什么在集合变换中没有旋转(我很感兴趣,因为它看起来很奇怪,而且倾斜对我来说似乎毫无用处),以及围绕画布中心旋转的最佳方法是什么同时使用setTransform

4

2 回答 2

9

setTransform is based on a 2D Matrix (3x3). These kinds of matrices are used for 2D/3D projections and are typically handled by game engines these days, rather than the programmers who make games.

These things are a little bit linear-algebra and a little bit calculus (for the rotation).

You're not going to like this a whole lot, but here's what you're looking at doing:

function degs_to_rads (degs) { return degs / (180/Math.PI); }
function rads_to_degs (rads) { return rads * (180/Math.PI); }

Start with these helper functions, because while we think well in degrees, computers and math systems work out better in radians.

Then you want to start with calculating your rotation:

var rotation_degs = 45,
    rotation_rads = degs_to_rads(rotation_degs),

    angle_sine = Math.sin(rotation_rads),
    angle_cosine = Math.cos(rotation_rads);

Then, based on the layout of the parameters:

ctx.setTransform(scaleX, skewY, skewX, scaleY, posX, posY);

in the following order, when rearranged into a transform matrix:

//| scaleX, skewX,  posX |
//| skewY,  scaleY, posY |
//| 0,      0,      1    |

...you'd want to submit the following values:

ctx.setTransform(angle_cosine, angle_sine, -angle_sine, angle_cosine, x, y);
// where x and y are now the "centre" of the rotation

This should get you rotation clockwise.

The marginal-benefit being that you should then be able to multiply everything by the scale that you initially wanted (don't multiply the posX and posY, though).

于 2012-07-22T22:40:16.270 回答
1

我根据#Norguard 的回答做了一些测试。

以下是在画布上绘制精灵的整个过程,包括平移、缩放、旋转(在旋转中心)和 alpha(不透明度):

var width = sprite.width;
var height = sprite.height;
var toX = sprite.transformOriginX * width;
var toY = sprite.transformOriginY * height;
// get the sin and cos value of rotate degree
var radian = sprite.rotate / 180 * Math.PI;
var sin = Math.sin(radian);
var cos = Math.cos(radian);

ctx.setTransform(
    cos * sprite.scaleX,
    sin * sprite.scaleX,
    -sin * sprite.scaleY,
    cos * sprite.scaleY,
    sprite.x + toX,
    sprite.y + toY
);
ctx.globalAlpha = sprite.alpha;
ctx.fillStyle = sprite.color;
ctx.fillRect(-toX, -toY, width, height);

我做了一个互动展示,你可以玩:

// prepare the context
var myCanvas = document.getElementById('myCanvas');
var ctx = myCanvas.getContext('2d');

// say we have a sprite looks like this
var sprite = {
  x: 50,
  y: 50,
  width: 50,
  height: 100,
  transformOriginX: 0.5, // the center of sprite width
  transformOriginY: 0.5, // the center of sprite height
  scaleX: 1.5,
  scaleY: 1,
  rotate: 45,
  alpha: 0.5, // opacity
  color: 'red'
};

function drawSprite() {

  var width = sprite.width;
  var height = sprite.height;
  var scaleX = sprite.scaleX;
  var scaleY = sprite.scaleY;
  // get the transform-origin value
  var toX = sprite.transformOriginX * width;
  var toY = sprite.transformOriginY * height;
  // get the sin and cos value of rotate degree
  var radian = sprite.rotate / 180 * Math.PI;
  var sin = Math.sin(radian);
  var cos = Math.cos(radian);

  ctx.setTransform(
    cos * scaleX,
    sin * scaleX,
    -sin * scaleY,
    cos * scaleY,
    sprite.x + toX,
    sprite.y + toY
  );
  ctx.globalAlpha = sprite.alpha;
  ctx.fillStyle = sprite.color;
  ctx.fillRect(-toX, -toY, width, height);
  
  if (toShowInfo) {
    ctx.globalAlpha = 1;
    
    ctx.beginPath();
    ctx.moveTo(-toX + width / 2, -toY + height / 2);
    ctx.lineTo(-toX + width / 2, -toY);
    ctx.strokeStyle = 'lime';
    ctx.stroke();
    
    ctx.beginPath();
    ctx.moveTo(-toX + width / 2, -toY + height / 2);
    ctx.lineTo(-toX + width, -toY + height / 2);
    ctx.strokeStyle = 'yellow';
    ctx.stroke();
  }
}

function draw() { // main launcher
  // rest the ctx
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
  ctx.fillStyle = 'white';
  ctx.font = '12px Arial';
  ctx.textAlign = 'end';
  ctx.textBaseline = 'hanging';
  ctx.fillText('made by Rex Hsu', 395, 5);
  // draw sprite
  drawSprite();
  // draw info
  if (toShowInfo) { drawInfo(); };
}

function drawInfo() {

  var x = sprite.x;
  var y = sprite.y;
  var width = sprite.width;
  var height = sprite.height;
  var toX = sprite.transformOriginX * width;
  var toY = sprite.transformOriginY * height;

  ctx.setTransform(1, 0, 0, 1, 0, 0);
  ctx.globalAlpha = 1;
  ctx.beginPath();
  ctx.arc(x + toX, y + toY, 3, 0, Math.PI * 2);
  ctx.fillStyle = 'lime';
  ctx.fill();
  
  ctx.font = '12px Arial';
  ctx.textAlign = 'start';
  ctx.textBaseline = 'middle';
  ctx.fillText('center of rotation', x + toX + 10, y + toY + 0);

  ctx.beginPath();
  ctx.rect(x, y, width, height);
  ctx.strokeStyle = 'lime';
  ctx.stroke();
}

function modifySprite() {

  var name = this.id;
  var value = this.value;
  
  if (name !== 'color') {
    value *= 1;
  }
  
  sprite[name] = value;
  draw();
}

// init
var toShowInfo = true;

document.getElementById('checkbox').onchange = function() {

  toShowInfo = !toShowInfo;
  draw();
};

var propsDom = document.getElementById('props');

for (var i in sprite) {
  var div = document.createElement('div');
  var span = document.createElement('span');
  var input = document.createElement('input');
  
  span.textContent = i + ':';
  input.id = i;
  input.value = sprite[i];
  input.setAttribute('type', 'text');
  input.addEventListener('keyup', modifySprite.bind(input));

  div.appendChild(span);
  div.appendChild(input);
  propsDom.appendChild(div);
}

draw();
body {
  font-family: monospace;
}

canvas {
  float: left;
  background-color: black;
}

div {
  float: left;
  margin: 0 0 5px 5px;
}

div > div {
  float: initial;
}

span {
  font-size: 16px;
}

input[type="text"] {
  margin: 0 0 5px 5px;
  color: #999;
  border-width: 0 0 1px 0;
}
<canvas id="myCanvas" width="400" height="400"></canvas>
<div id="props" style="float: left; width: calc(100% - 400px - 5px);">
  <div style="float: initial;">
    <input type="checkbox" id="checkbox" checked><span>Show origin-shape and the center of rotation</span>
  </div>
</div>

于 2017-09-21T08:36:24.723 回答