我想这可以被认为是这个问题的延续,所以请检查一下有关他的问题的上下文,尤其是上述问题中报告的JSFiddle。
我基本上试图实现相同的目标,但是我正在尝试绘制地理参考线(从点构建它们),但我仍然需要能够使光线投射器发挥作用。
我的主要问题是,如果我绘制它们时没有“移动”它们更靠近 THREE.js 世界的中心(通过使用下面的代码片段中提供的内容),否则渲染完全不精确(这些线需要更多的精度)或更少的 1/10 米看起来不错,并且由于 GPU 变换,它们的坐标被破坏了)。
...
this.center = MercatorCoordinate.fromLngLat(map.getCenter(), 0);
...
// lines is an array of lines, and each line is an array of georeferenced 3d points
lines.forEach((points) => {
const geometry = new THREE.BufferGeometry().setFromPoints(points.map(({lng, lat, elev}) => {
const { x, y, z } = MercatorCoordinate.fromLngLat([lng, lat], elev);
return new THREE.Vector3(x1 - center.x, y - center.y, z - center.z);
}));
const line = new THREE.Line(geometry, material);
this.scene.add(line);
});
因此,他们的可视化以这种方式正确工作,但前提是我不考虑在相机变换中缩放和旋转(因此通过在上面提到的小提琴中设置
this.cameraTransform = new THREE.Matrix4().setPosition(x, y, z);
)。
显然,通过使用这种方法,我认为确实需要使光线投射器工作的有关缩放和旋转的转换不再起作用。
无论在网上找到什么关于使用 lng/lat 坐标定位 3d 对象的解决方案,都是非常肤浅的,并且缺乏适当的文档,所以我无法真正弄清楚如何做到这一点......
有什么想法吗?
class GraphsLayer {
type = 'custom';
renderingMode = '3d';
constructor(id) {
this.id = id;
}
async onAdd (map, gl) {
this.map = map;
const { width, height } = map.transform;
this.camera = new THREE.PerspectiveCamera(28, width / height, 0.1, 1e6);
const centerLngLat = map.getCenter();
this.center = MercatorCoordinate.fromLngLat(centerLngLat, 0);
const { x, y, z } = this.center;
const s = this.center.meterInMercatorCoordinateUnits();
const scale = new THREE.Matrix4().makeScale(s, s, -s);
const rotation = new THREE.Matrix4().multiplyMatrices(
new THREE.Matrix4().makeRotationX(-0.5 * Math.PI),
new THREE.Matrix4().makeRotationY(Math.PI),
);
this.cameraTransform = new THREE.Matrix4().multiplyMatrices(scale, rotation).setPosition(x, y, z); // displaying of segments don't work with this
this.cameraTransform = new THREE.Matrix4().setPosition(x, y, z); // this instead works for displaying but not for the raycaster
this.scene = new THREE.Scene();
const material = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 3 });
const segments = await (await fetch('lines.json')).json();
segments.forEach((s) => {
const geometry = new THREE.BufferGeometry().setFromPoints(s.coos.map(p => {
const { x: x1, y: y1, z: z1 } = MercatorCoordinate.fromLngLat([p[0], p[1]], p[2]);
return new THREE.Vector3(x1 - x, y1 - y, z1 - z);
}));
const line = new THREE.Line(geometry, material);
this.scene.add(line);
});
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true,
});
this.renderer.autoClear = false;
this.raycaster = new THREE.Raycaster();
}
render (gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4()
.fromArray(matrix)
.multiply(this.cameraTransform);
this.renderer.resetState();
this.renderer.render(this.scene, this.camera);
}
raycast ({ x, y }) {
const { width, height } = this.map.transform;
const camInverseProjection = this.camera.projectionMatrix.clone().invert();
const cameraPosition = new THREE.Vector3().applyMatrix4(camInverseProjection);
const mousePosition = new THREE.Vector3(
(x / width) * 2 - 1, 1 - (y / height) * 2, 1,
).applyMatrix4(camInverseProjection);
const viewDirection = mousePosition.sub(cameraPosition).normalize();
this.raycaster.set(cameraPosition, viewDirection);
// calculate objects intersecting the picking ray
var intersects = this.raycaster.intersectObjects(this.scene.children, true);
console.log(intersects)
}
}
这适用于显示,但光线投射器不起作用。
我把所有东西都放在这里。
Maplibre-gl,这是一个与引用问题不同的库。他们的 API 不兼容,这解释了您遇到的问题。使用一些简单的调试即可清楚地看到两者。如果你console.log
方法中的一些变量,你会发现它们是错误的:
raycast
和
undefined
。
鼠标移动事件解构
NaN
mousemove
属性。至少在FF上是这样的,我没有检查任何其他浏览器。
point
画布尺寸您正在使用的 API 不同。 map.on('mousemove', (e) => graphsLayer.raycast(e.point)) // < add .point here
对象中没有 width
和
height
属性。您需要获取画布并将它们从那里提取出来。Map
当然,您稍后会使用新的变量名称。
const {
clientWidth,
clientHeight
} = this.map.getCanvas();
缩放和旋转
在我对原始问题的回答中,我强调
曾经(也许仍然是)使用与 OpenGL/WebGL/Three.js 不同的坐标系。因此需要缩放和旋转来补偿这一点。 Maplibre-gl,顾名思义,是面向OpenGL的,因此不需要更多的转换。这也是为什么你正确地发现,
省略旋转和缩放会产生正确的显示。
const mousePosition = new THREE.Vector3(
(x / clientWidth) * 2 - 1, 1 - (y / clientHeight) * 2, 1,
).applyMatrix4(camInverseProjection);
采摘门槛
由于线没有真实的表面,因此很少能找到视线与线的精确交点。相反,Three.js 计算射线到每条线的最小距离,如果低于给定阈值,则将其视为交点。params 属性
// No need for this
// const s = this.center.meterInMercatorCoordinateUnits();
// const scale = new THREE.Matrix4().makeScale(s, s, -s);
// const rotation = new THREE.Matrix4().multiplyMatrices(
// new THREE.Matrix4().makeRotationX(-0.5 * Math.PI),
// new THREE.Matrix4().makeRotationY(Math.PI),
// );
// this.cameraTransform = new THREE.Matrix4().multiplyMatrices(scale, rotation).setPosition(x, y, z);
// Just keep this
this.cameraTransform = new THREE.Matrix4().setPosition(x, y, z);
世界单位。 如果您查看光线投射器返回的场景比例和距离,即使鼠标距离很远,它们也位于
1
附近。你的空间是微观的。因此,为了获得鼠标指针最近邻域中的极少数线,您需要将阈值调整为类似的值。
2e-6