如何在 2 个单位向量之间弯曲单位网格

How to curve a unit mesh between 2 unit vectors

我正在尝试绘制 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 平面中

我可以在 canvas

中绘制这 2 个单位向量
  // 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 个单位向量之间的 space。鉴于它是一个单位网格,我似乎应该能够做到这一点

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;

如果我为每个网格点绘制 newXnewY,我似乎应该得到 2 个单位向量之间的正确弧线。

计算两个单位向量的点积应该得到它们之间夹角的余弦值,如果它们重合,则为 1,如果它们相反,则为 -1。在我的例子中,我需要 expand0PI 所以 (1 + -dot(p0, p1)) / 2 * PI 看起来应该可以工作。

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

我检查了一些东西。我检查了 zAxis 是正确的。它总是 [0,0,1][0,0,-1] 是正确的。我检查了 xAxisyAxis 是单位向量。他们是。我检查了手动将 expand 设置为 PI * .5PIPI * 2,它完全符合我的预期。 PI * .5 得到一个 90 度的弧,从蓝色单位向量开始绕 1/4 的距离。 PI 完全符合我的预期得到一个半圆。 PI * 2 得到一个完整的圆。

这看起来 dot(p0,p1) 是错误的,但查看 dot 函数似乎是正确的,如果用各种简单的向量测试它 returns 我期望的 dot([1,0,0], [1,0,0]) returns 1. dot([-1,0,0],[1,0,0]) returns -1。 dot([1,0,0],[0,1,0]) returns 0. dot([1,0,0],normalize([1,1,0])) returns 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>

你的dot产品功能没有问题。这就是您使用它的方式:

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

应该是:

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

您使用的 expand 变量是一个角度(以弧度为单位)。 dot 乘积为您提供角度的余弦值,但不是角度本身。虽然输入 [0,pi] 的角度余弦值在 1 和 -1 之间变化,但该值不会线性映射回角度本身。

换句话说,它不起作用,因为角度的余弦不能简单地通过缩放来转换为角度本身。这就是反正弦的用途。

请注意,一般来说,如果您只需要一个近似值,但它永远不会给出精确的角度,除非在极端情况下。

这个可以直观的看到plotting the two functions on top of each other: