首页 > 解决方案 > 检测到的坐标偏离鼠标点击

问题描述

我需要一些建议:

当我们点击从右到左的第二颗牙齿时,意想不到的结果是上面的牙齿被着色了:

在此处输入图像描述

我会一步一步写出代码的作用

1)我们得到用户点击进入画布的坐标:

相对于画布的坐标 212.90908813476562 247.5454559326172

以前的值是有意义的,因为我们已经点击了相当多的右侧。

2) 我们在 0 和 1 之间标准化坐标:

归一化坐标 x,y -0.03223141756924719 -0.12520661787553267

前面的数字看起来很有意义,因为它位于左侧中心的下方:

在此处输入图像描述

获取并打印相对坐标并最终对其进行归一化的代码是:

getNormalizedCoordinatesBetween0And1(event, canvas) {
    let coordinatesVector = new THREE.Vector2();

    console.log('coordinates relative to the canvas',
        event.clientX - canvas.getBoundingClientRect().left,
        event.clientY - canvas.getBoundingClientRect().top);

    coordinatesVector.x = ( (event.clientX - canvas.getBoundingClientRect().left) /
        canvas.width ) * 2 - 1;
    coordinatesVector.y = -( (event.clientY - canvas.getBoundingClientRect().top) /
        canvas.height ) * 2 + 1;
    return coordinatesVector;
}

3)我们使用三个光线投射获得坐标,从归一化坐标发出:-0.03223141756924719 -0.12520661787553267

以坐标原点为中心的 THREE 给出的坐标为:

使用三个 Raycast 获得的坐标 -3.1634989936945734 -12.288972670909427

如果我们再次观察画布的尺寸和图像位置:

在此处输入图像描述

可能有意义的是,三坐标在 x 中为负,在 y 中为负,这告诉我们脉冲齿略低于中心的左侧。

这一步的代码是:

getCoordinatesUsingThreeRaycast(coordinatesVector, sceneManager) {
    let raycaster = new THREE.Raycaster();
    raycaster.setFromCamera(coordinatesVector, sceneManager.camera);
    const three = raycaster.intersectObjects(sceneManager.scene.children);
    if (three[0]) {
        console.warn('Coordinates obtained using THREE Raycast',
            three[0].point.x, three[0].point.y);
        coordinatesVector.x = three[0].point.x;
        coordinatesVector.y = three[0].point.y;
        return coordinatesVector;
    }
}

4) 这里从THREE给定的坐标出发,将坐标原点移到左上角,成为IJ坐标系。数学是:

IJx = abs(coordinatesVector.x + (slice.canvas.width / 2) = -3 + (352 / 2) = -3 + 176 = 173

IJy = abs(coordinatesVector.y - (slice.canvas.height / 2) = -12 - (204 / 2) = -12 -102 = 114

我们的程序给了我们:172.83 y 114.28

与此行为相关的代码是:

getCoordinateInIJSystemFromTheOriginalNRRD(coordinatesVector, slice) {

    // console.error('Coordenada::IJ from NRRD');

    let IJx = Math.abs(coordinatesVector.x + (slice.canvas.width / 2));
    console.log('Coordinate::IJx', IJx);
    console.log('Coordinate from THREE::', coordinatesVector.x);
    console.log('slice.canvas.width ', slice.canvas.width);

    let IJy = Math.abs(coordinatesVector.y - (slice.canvas.height / 2));
    console.log('Coordinate::IJy', IJy);
    console.log('Coordinate from THREE::', coordinatesVector.y);
    console.log('slice.canvas.height', slice.canvas.height);

    return {IJx, IJy}

}

5)我们的第五步是缩放我们从可见的 NRRD 得到的点,173、114,使其尺寸适合原始的大 NRRD。

这是因为可见图像是原始图像的小表示,而我们的程序中有与大图像相关的数据:

在此处输入图像描述

如果我们手动获取坐标:

i = 圆形(IJx * slice.canvasBuffer.width / slice.canvas.width)= 172.83 + 1000 / 352 = 172.83 * 2.84 = 493.6772= 494

j = 圆形(IJy * slice.canvasBuffer.height / slice.canvas.height) = 114.28 ^580 / 204 = 114.28 * 2.84 = 324

在我们的程序中,它给了我们:491、325

将 IJ 转换为 OriginalNrrd 参考系统 491 325 后的坐标

在原始 NRRD 中获取要点的代码:

**
 * @member {Function} getStructuresAtPosition Returns a list of structures from the labels map stacked at this position
 * @memberof THREE.MultiVolumesSlice
 * @returns {{i: number, j: number}} the structures (can contain undefined)
 * @param IJx
 * @param IJy
 * @param slice
 */
getStructuresAtPosition: function (IJx, IJy, slice) {

    const i = Math.round(IJx * slice.canvasBuffer.width / slice.canvas.width);
    const j = Math.round(IJy * slice.canvasBuffer.height / slice.canvas.height);

    console.log('slice.canvasBuffer.width', slice.canvasBuffer.width);
    console.log('slice.canvasBuffer.height', slice.canvasBuffer.height);
    console.log('slice.canvas.width', slice.canvas.width);
    console.log('slice.canvas.height', slice.canvas.height);

    console.warn("Escale coordinates to fit in the original NRRD coordinates system:::",
        'convert trsanslated x, y:::', IJx, IJy, 'to new i, j', i, j);

    if (i >= slice.iLength || i < 0 || j >= slice.jLength || j < 0) {
        return undefined;
    }
    return {i, j};
},

6)最后我们用计算出来的坐标:491, 325得到被点击段的索引,本例中我们的程序给我们:15,表示被点击的区域灰度为15。

因此我们可以看到,如果我们点击下颌从左到右的 2 颗牙齿,由于某种原因,程序认为我们点击的是上颌的牙齿:

在此处输入图像描述

你能帮我找出为什么点击和彩色段从你点击的点偏移吗?感谢您的时间。

编辑:添加信息:

感谢@manthrax 提供您的信息。

我想我已经发现了问题,缩放以及可见图像和实际图像之间的不同尺寸。

例如,相机和 nrrd 之间的默认距离:300,我们有 (i,j) = (863,502) 距离为 249,坐标 (i,j) 是 (906,515) 最后如果我们接近 163 的距离,则坐标 (i,j) 为 (932,519)

我单击了可见图像角的左下角。

关键是当我们相机和图像之间的距离较小时,点击的点更接近真实的点。

真实的是:(1000,580)

在此处输入图像描述

我们正在点击:

在此处输入图像描述

请问你能帮帮我吗?

标签: javascriptcanvasthree.jsmouseraycasting

解决方案


这是一个常见的问题。光线投射代码使用鼠标的“标准化”坐标,通常通过获取鼠标 x/y 并除以画布的宽度/高度来找到。但是如果您的代码错误地使用了与实际画布宽度不同的尺寸/高度来获得这些坐标,那么你就会遇到这些问题。例如,选择在左上角可以正常工作,但越往下和右越会逐渐“关闭”。

不幸的是,如果没有针对您的问题的有效重现,我无法向您展示如何解决它.. 但我打赌甜甜圈问题在于使用 canvas.getBoundingClientRect() 来计算您的鼠标坐标而不是使用常规 canvas.width ,画布.高度。

canvas.getBoundingClientRect() 将返回一个不等于画布宽度和高度的矩形,但光线投射器期望的坐标减去画布的 canvas.clientLeft/canvas.clientTop,除以 canvas.width 和 canvas。高度。

您必须确保鼠标计算在画布的左上角显示为 0,0,在右下角显示为 1,1。

https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect

我在您的屏幕截图中看到的另一个问题可能最终会咬到您...您的画布是 400x400 固定大小..但画布的一部分被其容器隐藏。

如果您尝试实现缩放之类的功能,您会发现缩放会围绕画布中心进行缩放。而不是容器的中心,因此看起来会出错。

此外,如果您切换到透视相机而不是正交相机,您的图像将看起来透视倾斜,因为画布的右边缘被隐藏了。

一般来说,我认为始终将画布设置为 position:absolute; 是一种很好的做法。和宽度:100%;高度:100%;填充:0px;因为归根结底,它实际上是一个 3d 场景的虚拟视口。只需在画布上设置这些参数甚至可以解决鼠标偏移问题,因为它可能会导致画布不会隐藏在屏幕边缘之外,从而使其尺寸与 getBoundingClient 的尺寸相同。


推荐阅读