如何在@react-three/drei地球模型上绘制地理坐标连接曲线?
问题描述
我基于一个3D地球模型项目搭建了自己的版本,已经为地理坐标添加了标记。现在想要创建曲线连接部分标记,实现类似飞行轨迹的效果。查阅drei文档后,我选择使用CatmullRomLine组件,编写了如下CurvedLine组件:
import React, { useRef } from 'react'; import { CatmullRomLine } from '@react-three/drei'; export interface CurvedLineProps { start: [number, number, number]; end: [number, number, number]; } export const CurvedLine = ({ start, end }: CurvedLineProps) => { const lineRef = useRef(null); return ( <CatmullRomLine ref={lineRef} points={[start, end]} closed={false} color={0xffe600} lineWidth={6} curveType='catmullrom' tension={0.03} /> ); };
随后在App.tsx中调用该组件:
import { CurvedLine} from './components/CurvedLine'; const getLinePoint = (item): [number, number, number] => { return [item.lat as number, item.lon as number, 0]; }; // ...其余代码 <Suspense fallback={null}> <CurvedLine start={getLinePoint(coords[0])} end={getLinePoint(coords[3])} /> <Earth coords={coords} /> </Suspense>
目前线条已成功绘制,但存在两个问题:一是线条不在地球模型附近,二是没有预期的弯曲效果,实际显示如下:
我尝试移除硬编码的0,但结果没有变化。请问问题出在哪里?如何正确绘制连接两点的弧形飞行轨迹?
解决方案
1. 地理坐标未转换为3D球面坐标
当前直接将经纬度[lat, lon, 0]作为3D坐标传入曲线组件,但这是二维地理坐标,并未映射到地球模型的3D球面空间中。地球模型基于球面构建,需要将经纬度转换为Three.js可识别的笛卡尔坐标(x, y, z)。
可以编写如下转换函数:
// 地球半径根据你的模型缩放比例调整,设为1.01是让曲线略高于地球表面,避免被遮挡 const latLonTo3D = (lat: number, lon: number, radius = 1.01): [number, number, number] => { const phi = (90 - lat) * (Math.PI / 180); const theta = (lon + 180) * (Math.PI / 180); return [ -(radius * Math.sin(phi) * Math.cos(theta)), radius * Math.cos(phi), radius * Math.sin(phi) * Math.sin(theta) ]; };
然后修改getLinePoint函数,使用转换后的坐标:
const getLinePoint = (item): [number, number, number] => { return latLonTo3D(item.lat as number, item.lon as number); };
2. 仅两点无法生成CatmullRom曲线
CatmullRomLine需要至少3个点才能生成平滑的曲线,仅传入起点和终点时,组件只会绘制一条直线。要实现飞行轨迹的弧形效果,需要在两点之间添加中间控制点,让曲线向外凸起。
可以通过计算两点中点并向外拉伸的方式生成控制点:
// 生成包含起点、凸起中点、终点的曲线点数组 const getCurvePoints = (start: [number, number, number], end: [number, number, number]) => { // 计算两点的笛卡尔中点 const midX = (start[0] + end[0]) / 2; const midY = (start[1] + end[1]) / 2; const midZ = (start[2] + end[2]) / 2; // 将中点沿径向向外拉伸,形成凸起效果 const midPoint = [midX * 1.2, midY * 1.2, midZ * 1.2] as [number, number, number]; return [start, midPoint, end]; };
接着修改CurvedLine组件,使用包含控制点的数组:
export const CurvedLine = ({ start, end }: CurvedLineProps) => { const lineRef = useRef(null); const curvePoints = getCurvePoints(start, end); return ( <CatmullRomLine ref={lineRef} points={curvePoints} closed={false} color={0xffe600} lineWidth={6} curveType='catmullrom' tension={0.5} // 提高张力值,让曲线过渡更平滑 /> ); };
3. 调整渲染层级避免遮挡
如果曲线被地球模型遮挡,可以调整渲染顺序(将CurvedLine放在Earth组件之后渲染),或者修改曲线材质的深度测试属性:
<CatmullRomLine // ...其他属性 material={{ depthTest: false }} />
内容的提问来源于stack exchange,提问作者Igal
相关产品推荐
相关产品推荐

