1

我正在尝试绘制 2 个单位向量,然后在它们之间画一条弧线。我不是在寻找任何解决方案,而是想知道为什么我的特定解决方案不起作用。

首先我随机选择 2 个单位向量。

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

var points = [{},{}];

points[0].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
points[1].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);

注意:这里的数学是 3D 的,但我使用的是 2d 示例,只需将向量保持在 XY 平面中

我可以在画布上绘制这两个单位向量

  // move to center of canvas
  var scale = ctx.canvas.width / 2 * 0.9;
  ctx.transform(ctx.canvas.width / 2, ctx.canvas.height / 2);
  ctx.scale(scale, scale);  // expand the unit fill the canvas

  // draw a line for each unit vector
  points.forEach(function(point) {
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(point.direction[0], point.direction[1]);
    ctx.strokeStyle = point.color;
    ctx.stroke();
  });

这样可行。

接下来我想制作一个矩阵,将 XY 平面的 Y 轴与第一个单位向量对齐,并与 2 个单位向量描述的平面位于同一平面

  var zAxis = normalize(cross(points[0].direction, points[1].direction));
  var xAxis = normalize(cross(zAxis, points[0].direction));
  var yAxis = points[0].direction;

然后我使用该矩阵绘制一个单位网格

ctx.setTransform(
    xAxis[0] * scale, xAxis[1] * scale,
    yAxis[0] * scale, yAxis[1] * scale,
    ctx.canvas.width / 2, ctx.canvas.height / 2);

ctx.beginPath();
for (var y = 0; y < 20; ++y) {
  var v0 = (y + 0) / 20;
  var v1 = (y + 1) / 20;
  for (var x = 0; x < 20; ++x) {
    var u0 = (x + 0) / 20;
    var u1 = (x + 1) / 20;
    ctx.moveTo(u0, v0);
    ctx.lineTo(u1, v0);
    ctx.moveTo(u0, v0);
    ctx.lineTo(u0, v1);
  }
}
ctx.stroke();

这也有效。运行下面的示例,看到粉红色的单位网格始终与绿色单位向量对齐,并朝向红色单位向量的方向。

最后使用单位网格的数据,我想将其弯曲正确的量以填充 2 个单位向量之间的空间。鉴于它是一个单位网格,我似乎应该能够做到这一点

var cosineOfAngleBetween = dot(points[0].direction, points[1].direction);
var expand = (1 + -cosineOfAngleBetween) / 2 * Math.PI;
var angle = x * expand;      // x goes from 0 to 1
var newX = sin(angle) * y;   // y goes from 0 to 1
var newY = cos(angle) * y;

如果我绘制newX并且newY对于每个网格点,我似乎应该在 2 个单位向量之间得到正确的弧。

取两个单位向量的点积应该给我它们之间角度的余弦,1如果它们重合,-1则它们是否相反。就我而言,我需要expand0到 ,PI所以(1 + -dot(p0, p1)) / 2 * PI它似乎应该工作。

但事实并非如此。请参阅作为上述代码输入的单位网格点的蓝色弧线。

我检查了一些东西。我检查zAxis是正确的。它总是要么[0,0,1]正确,要么[0,0,-1]正确。我检查xAxis并且yAxis是单位向量。他们是。我检查了手动设置expandPI * .5, PIPI * 2它完全符合我的预期。PI * .5得到一个 90 度的弧线,距离蓝色单位向量的 1/4。PI完全符合我的预期。PI * 2得到一个完整的圆圈。

这让它看起来dot(p0,p1)是错误的,但看看这个dot函数似乎是正确的,如果用各种简单的向量测试它,它会返回我期望的dot([1,0,0], [1,0,0])返回 1。dot([-1,0,0],[1,0,0])返回 -1。dot([1,0,0],[0,1,0])返回 0.dot([1,0,0],normalize([1,1,0]))返回 0.707...

我错过了什么?

这是实时的代码

function cross(a, b) {
  var dst = []

  dst[0] = a[1] * b[2] - a[2] * b[1];
  dst[1] = a[2] * b[0] - a[0] * b[2];
  dst[2] = a[0] * b[1] - a[1] * b[0];

  return dst;
}

function normalize(a) {
  var dst = [];

  var lenSq = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
  var len = Math.sqrt(lenSq);
  if (len > 0.00001) {
    dst[0] = a[0] / len;
    dst[1] = a[1] / len;
    dst[2] = a[2] / len;
  } else {
    dst[0] = 0;
    dst[1] = 0;
    dst[2] = 0;
  }

  return dst;
}

function dot(a, b) {
 return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
}

var canvas = document.querySelector("canvas");
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

var points = [
  {
    direction: [0,0,0],
    color: "green",
  },
  {
    direction: [0,0,0],
    color: "red",
  },
];

var expand = 1;
var scale = ctx.canvas.width / 2 * 0.8;

function pickPoints() {
  points[0].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
  points[1].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
  expand = (1 + -dot(points[0].direction, points[1].direction)) / 2 * Math.PI;
  console.log("expand:", expand);

  render();
}
pickPoints();

function render() {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.save();
  ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
  ctx.scale(scale, scale);

  ctx.lineWidth = 3 / scale;
  points.forEach(function(point) {
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(point.direction[0], point.direction[1]);
    ctx.strokeStyle = point.color;
    ctx.stroke();
  });

  var zAxis = normalize(cross(points[0].direction, points[1].direction));
  var xAxis = normalize(cross(zAxis, points[0].direction));
  var yAxis = points[0].direction;

  ctx.setTransform(
      xAxis[0] * scale, xAxis[1] * scale,
      yAxis[0] * scale, yAxis[1] * scale,
      ctx.canvas.width / 2, ctx.canvas.height / 2);

  ctx.lineWidth = 0.5 / scale;

  ctx.strokeStyle = "pink";
  drawPatch(false);
  ctx.strokeStyle = "blue";
  drawPatch(true);

  function drawPatch(curved) {
    ctx.beginPath();
    for (var y = 0; y < 20; ++y) {
      var v0 = (y + 0) / 20;
      var v1 = (y + 1) / 20;
      for (var x = 0; x < 20; ++x) {
        var u0 = (x + 0) / 20;
        var u1 = (x + 1) / 20;
        if (curved) {
          var a0 = u0 * expand;
          var x0 = Math.sin(a0) * v0;
          var y0 = Math.cos(a0) * v0;
          var a1 = u1 * expand;
          var x1 = Math.sin(a1) * v0;
          var y1 = Math.cos(a1) * v0;
          var a2 = u0 * expand;
          var x2 = Math.sin(a0) * v1;
          var y2 = Math.cos(a0) * v1;
          ctx.moveTo(x0, y0);
          ctx.lineTo(x1, y1);
          ctx.moveTo(x0, y0);
          ctx.lineTo(x2, y2);
        } else {
          ctx.moveTo(u0, v0);
          ctx.lineTo(u1, v0);
          ctx.moveTo(u0, v0);
          ctx.lineTo(u0, v1);
        }
      }
    }
    ctx.stroke();
  }

  ctx.restore();
}

window.addEventListener('click', pickPoints);
canvas {
    border: 1px solid black;
}
div {
  display: flex;
}
<div><canvas></canvas><p> Click for new points</p></div>

4

1 回答 1

2

There's nothing wrong with your dot product function. It's the way you're using it:

expand = (1 + -dot(points[0].direction, points[1].direction)) / 2 * Math.PI;

should be:

expand = Math.acos(dot(points[0].direction, points[1].direction));

The expand variable, as you use it, is an angle (in radians). The dot product gives you the cosine of the angle, but not the angle itself. While the cosine of an angle varies between 1 and -1 for input [0,pi], that value does not map linearly back to the angle itself.

In other words, it doesn't work because the cosine of an angle cannot be transformed into the angle itself simply by scaling it. That's what arcsine is for.

Note that in general, you can often get by using your original formula (or any simple formula that maps that [-1,1] domain to a range of [0,pi]) if all you need is an approximation, but it will never give an exact angle except at the extremes.

这可以通过将两个函数相互叠加来直观地看到:

资料来源:WolframAlpha

于 2016-03-01T19:10:15.460 回答