5

我有一个元素,它已经用 a 进行了转换matrix3d以赋予它透视。它代表手持设备的屏幕。

有一个显示手持设备本身的背景图像,这没有被转换。保存它的元素被定位,屏幕元素绝对定位在其中,在 处left: 0; top: 0;,然后转换,原点在容器的左上角。这是我将它与背景图像完美对齐的最简单方法(我使用这个非常方便的工具来制作矩阵),并且它将屏幕元素从角落移开。

我希望能够通过鼠标和触摸事件与屏幕进行交互。为此,在单击或触摸事件中,我需要在屏幕元素的本地坐标系中找到坐标——即发生变换之前的坐标。换句话说,当单击手持设备屏幕的左上角(不是页面上边界框的左上角!)[0, 0]时,当单击屏幕的右上角时,在这种变换的情况下实际上在页面的上方和右侧,我想要[untransformedWidth, 0]

鼠标事件提供offsetX并且offsetY据称确实做到了这一点(更多内容见下文),但触摸事件没有这些属性,所以我需要一种自己计算它们的方法。

使用math.js我可以输入转换矩阵并将其反转。我有一些代码可以遍历 CSS 规则以获取transform: matrix3d(...)规则(如果不需要,我不想在我的代码中重复它),我会跳过它——我知道它有效,因为数字匹配 CSS。

请注意,CSS 的矩阵元素按列顺序排列,因此matrix3d(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)使用常规矩阵表示法如下所示:

┌            ┐
│ a  e  i  m │
│ b  f  j  n │
│ c  g  k  o │
│ d  h  l  p │
└            ┘

同时,math.js 希望它的矩阵逐行声明,比如[[a, e, i, m], [b, f, j, n]....

因此,从matrix3d(...)表达式内部的数字元素列表开始,按照 CSS 顺序,我正在构建和反转矩阵,如下所示:

var rows = [[], [], [], []];
for (var i = 0; i < elements.length; i++) {
  rows[i % 4][Math.floor(i / 4)] = elements[i];
}

var matrixTransform = math.matrix(rows);

var invertedMatrixTransform = math.inv(matrixTransform);

然后我在屏幕元素上设置了一个鼠标事件处理程序:

screen.addEventListener('mousedown', function (event) {
  var rect = container.getBoundingClientRect();
  var x = event.clientX - rect.left;
  var y = event.clientY - rect.top;

如果我将标记(相对于容器)移动到这一点[x, y],它就会出现在我单击的位置。所以我知道这很有效。然后我将这些坐标的向量乘以逆变换矩阵:

  var vector = math.matrix([x, y, 0, 1]);
  var result = math.multiply(inverseMatrixTransform, vector);

如果我将另一个标记(这个相对于屏幕元素的标记)移动到结果向量的值[result.get([0]), result.get([1])],它会移动到与前一个标记大致相同的位置,但它并不完全正确。似乎离原点越远,错误就越多,直到右侧和底部边缘确实非常糟糕。

但是,如果我检查offsetXandoffsetY怎么办?好吧,事实证明答案取决于浏览器。

在 Firefox 中,找到的坐标offset*也不匹配点击的位置。它们与我计算的不完全相同,但仅相差几个像素。它们与我的计算值一样远离真正的点击点。

Firefox 45 中的示例输出

但在 Chrome 中找到的坐标offset*是完美的。

Chrome 中的示例输出

这是一个jsfiddle

我的计算有什么问题吗?有没有办法让我模仿 Chrome 的结果,但没有offset*属性?

4

1 回答 1

3

我对这个理论并不完全确定,我在处理类似问题时读到了这个,但这是我发现的。矩阵的乘法(3d 变换的矩阵 - 齐次坐标和光标的位置)产生除 x 和 y 之外的另外两个值。第一个是 z,另一个是 w,用于将 3d 对象投影到 2d 平面上。

如果将向量的 x 和 y 值除以 w 坐标,则可以得到光标在笛卡尔平面上的正确位置/映射。

所以,我会用这些替换你小提琴中的代码,第 69-70 行:

var x = result.get([0]);
var y = result.get([1]);
var w = result.get([3]);
screenCrosshair.style.left = x / w + 'px';
screenCrosshair.style.top = y / w + 'px';

这是更新小提琴: https ://jsfiddle.net/x3ruc3tL/1/

然后你不需要浏览器的offsetXoffsetY值来找到正确的位置。

请阅读以下文章以获取更多信息:

https://en.wikipedia.org/wiki/3D_projection#Perspective_projection http://deltaorange.com/2012/03/08/the-truth-behind-homogenous-coordinates/

于 2017-02-14T09:53:38.560 回答