我正在尝试绘制 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
则它们是否相反。就我而言,我需要expand
从0
到 ,PI
所以(1 + -dot(p0, p1)) / 2 * PI
它似乎应该工作。
但事实并非如此。请参阅作为上述代码输入的单位网格点的蓝色弧线。
我检查了一些东西。我检查zAxis
是正确的。它总是要么[0,0,1]
正确,要么[0,0,-1]
正确。我检查xAxis
并且yAxis
是单位向量。他们是。我检查了手动设置expand
为PI * .5
, PI
,PI * 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>