osgearth 设置物体的旋转

一、前言

        我们知道osg使用的是右手系。
其坐标轴定义为:
        X轴:向右(Right)、Y轴:向上(Up)、Z轴:向前(Forward)。
欧拉角的定义为:
        Heading(航向角)绕 Y轴 旋转。控制物体的 左右转向(如飞机绕垂直轴旋转)。
        Pitch(俯仰角)绕 X轴 旋转。控制物体的 上下俯仰(如飞机抬头或低头)。
        Roll(翻滚角)绕 Z轴 旋转。控制物体的 左右翻滚(如飞机绕机身纵轴旋转)。

而osgEarth 基于 OpenSceneGraph(OSG),但在地理空间中扩展了地理坐标系(如 WGS84)。其默认本地坐标系通常使用 ENU(东-北-天)坐标系。 
其坐标系定义为:
X轴:正东方向(East)、Y轴:正北方向(North)、Z轴:垂直向上(Up,天顶方向)。
虽然都是用了右手坐标系,但是坐标轴的朝向发生的变化。如下图所示:

x轴朝右,y指向正北,z轴垂直于地面,这里使用默认的ENU(东北天)坐标系。
osgearth的欧拉角定义为:
        Heading(航向角),绕 Z轴(垂直向上)旋转。
        Pitch(俯仰角),绕 X轴(东方向)旋转。
        Roll(翻滚角),绕 Y轴(北方向)旋转。

二、关键代码

有了以上的了解,设置一个物体的旋转直接使用内置的接口就好:

	//1)构建旋转矩阵
    osg::Matrix rotationMatrix;
	rotationMatrix.makeRotate(
    	osg::inDegrees(m_stRotation.dPitch), osg::Vec3d(1, 0, 0),
		osg::inDegrees(m_stRotation.dRoll), osg::Vec3d(0, 1, 0),
		osg::inDegrees(m_stRotation.dHeading), osg::Vec3d(0, 0, 1));
    //2)应用到节点
    osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
	mt->setMatrix(rotationMatrix);
    //3)添加到自己的节点
    mt->addChild(xxxx);//自己的节点 

注意顺序是以x轴、y轴、z轴的顺序,而不是heading、pitch、roll的顺序,这里不得不吐槽一下源码命名的随意性,说好的见名知意呢?让不熟悉的人使用这个接口,传值的顺序只能靠去猜了...

我直接上来就是heading、pitch、roll的顺序,错的找不到北(:0

三、效果

最后附上画局部坐标之后的代码

//创建坐标轴
osg::ref_ptr<osg::MatrixTransform> static CreateDebugAxis(double lngD, double latD, double height, double length = 1000000)
{
	osg::EllipsoidModel ellipsoid;
	// 计算ENU到世界坐标的变换矩阵
	osg::Matrixd localToWorld;
	ellipsoid.computeLocalToWorldTransformFromLatLongHeight(osg::inDegrees(latD), osg::inDegrees(lngD), height, localToWorld);

	// 创建变换节点并设置矩阵
	osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
	mt->setMatrix(localToWorld);

	mt->addChild(WHRadar::Util::GetDebugLine(osg::Vec3(0.0, 0.0, 0.0), osg::Vec3(length, 0.0, 0.0), osg::Vec4(255.0, 0.0, 0.0, 255.0)));
	mt->addChild(WHRadar::Util::GetDebugLine(osg::Vec3(0.0, 0.0, 0.0), osg::Vec3(0.0, length, 0.0), osg::Vec4(0.0, 255.0, 0.0, 255.0)));
	mt->addChild(WHRadar::Util::GetDebugLine(osg::Vec3(0.0, 0.0, 0.0), osg::Vec3(0.0, 0.0, length), osg::Vec4(0.0, 0.0, 255.0, 255.0)));
	return mt;
}

//获取调试线
osg::ref_ptr<osg::Geode>  static  GetDebugLine(osg::Vec3 point1, osg::Vec3 point2, osg::Vec4 color = osg::Vec4(255.0f, 0.0f, 0.0f, 1.0f))
{
	osg::ref_ptr<osg::Geode> geode = new osg::Geode();
	osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
	osg::ref_ptr<osg::Vec3Array> points = new osg::Vec3Array();

	points->push_back(point1);
	points->push_back(point2);

	geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, 2));
	geometry->setVertexArray(points);

	osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
	colors->push_back(color);
	colors->push_back(color);

	geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
	geometry->getOrCreateStateSet()->setAttribute(new osg::LineWidth(1.0), osg::StateAttribute::ON);

	geode->addDrawable(geometry.get());

	return geode;
}
//添加到场景
m_pRoot->addChild(WHRadar::Util::CreateDebugAxis(105, 35, 32000));

原创不易,记得点赞加关注哦,我会持续分享实用的功能(:-

<think>我们正在讨论osgEarth中的旋转矩阵与实体姿态(通常用位置和方向表示)之间的转换。 实体姿态通常包括位置(平移)和方向(旋转)。在osgEarth中,我们通常使用一个4x4的变换矩阵(osg::Matrixd)来表示,其中左上角3x3是旋转矩阵,最后一列的前三个元素是平移向量。 1. 从姿态(位置和方向)到旋转矩阵(实际上是变换矩阵): 假设我们有一个位置(x, y, z)和一个表示方向的四元数(osg::Quat),我们可以这样构建变换矩阵: osg::Matrixd matrix; matrix.makeRotate(quat); // 设置旋转部分 matrix.setTrans(x, y, z); // 设置平移部分 注意:这里构建的矩阵实际上是先旋转后平移。 2. 从旋转矩阵(变换矩阵)中提取姿态: 我们可以从变换矩阵中提取位置和方向(四元数)。 osg::Vec3d position = matrix.getTrans(); osg::Quat quat = matrix.getRotate(); 但是,在osgEarth中,我们经常需要处理地理空间中的实体。因此,我们还需要考虑地理坐标系(如WGS84)和局部坐标系之间的转换。 3. 地理空间实体的姿态: 对于地理空间中的实体,我们通常使用经纬度高程(LLA)表示位置,而方向则可以用相对于地理北(航向)、俯仰和横滚(欧拉角)表示,或者用相对于地心坐标系的四元数表示。 a. 从LLA和欧拉角(航向、俯仰、横滚)到变换矩阵: 首先,将LLA(经纬度高程)转换为地心坐标系(ECEF)中的位置: osgEarth::GeoPoint geoPoint(mapSRS, lon, lat, alt); osg::Vec3d ecefPosition; geoPoint.toWorld(ecefPosition); 然后,我们需要构建一个从局部东北天(ENU)坐标系到地心坐标系的变换矩阵: osg::Matrixd localToWorld; geoPoint.createLocalToWorld(localToWorld); 接下来,构建一个表示航向、俯仰、横滚的旋转矩阵(注意顺序): osg::Matrixd rotMatrix; // 注意:欧拉角的顺序通常为航向(绕Z轴)、俯仰(绕Y轴)、横滚(绕X轴),但具体顺序取决于定义 // 假设顺序为:航向(heading,绕Z轴旋转)、俯仰(pitch,绕Y轴旋转)、横滚(roll,绕X轴旋转) // 注意:在ENU坐标系中,航向是绕Z轴(向上)旋转,俯仰是绕东(Y轴)旋转,横滚是绕北(X轴)旋转? // 但实际上,在ENU坐标系中,通常定义:X指向东,Y指向北,Z指向天。而航向是绕Z轴(天)旋转,俯仰是绕东(X轴)旋转,横滚是绕北(Y轴)旋转?这需要根据具体定义调整。 // 更常见的做法是:先构建一个从物体坐标系到ENU坐标系的旋转矩阵,然后通过局部到世界的矩阵(localToWorld)将整个变换转换到地心坐标系。 // 另一种做法:使用osg::Matrix::rotate按顺序旋转。注意旋转顺序:通常是航向、俯仰、横滚(但顺序很重要,一般按ZYX顺序?) // 这里我们使用四元数组合: osg::Quat quatHeading = osg::Quat(osg::DegreesToRadians(heading), osg::Vec3d(0,0,1)); // 航向绕Z轴 osg::Quat quatPitch = osg::Quat(osg::DegreesToRadians(pitch), osg::Vec3d(1,0,0)); // 俯仰绕X轴(东) osg::Quat quatRoll = osg::Quat(osg::DegreesToRadians(roll), osg::Vec3d(0,1,0)); // 横滚绕Y轴(北) osg::Quat rotENU = quatRoll * quatPitch * quatHeading; // 注意顺序:先航向,再俯仰,后横滚(但乘法顺序相反:从右向左) // 构建从物体坐标系到ENU坐标系的旋转矩阵 osg::Matrixd objToENU; objToENU.setRotate(rotENU); // 然后,将物体坐标系到ENU的变换与ENU到地心坐标系的变换(localToWorld)组合: osg::Matrixd objToWorld = localToWorld * objToENU; // 最后,将位置设置进去(注意:localToWorld已经包含了位置,但我们可能希望将位置单独设置?) // 实际上,我们可以在objToWorld中设置平移部分,但localToWorld已经包含了平移。所以上面组合后,平移部分就是localToWorld的平移(即位置)。 // 如果我们还需要在物体坐标系中有平移(比如模型中心不在原点),则还需要一个平移矩阵。 b. 从变换矩阵中提取LLA和欧拉角: 首先,从变换矩阵中提取地心坐标系下的位置: osg::Vec3d ecefPosition = matrix.getTrans(); 然后,将地心坐标转换为经纬度高程: osgEarth::GeoPoint geoPoint; geoPoint.fromWorld(mapSRS, ecefPosition); double lon = geoPoint.x(); double lat = geoPoint.y(); double alt = geoPoint.z(); 提取方向(欧拉角)则更复杂,因为需要知道相对于ENU坐标系的旋转。我们可以这样做: // 首先,计算当前位置的ENU坐标系到地心坐标系的旋转矩阵(即localToWorld的旋转部分) osg::Matrixd localToWorld; geoPoint.createLocalToWorld(localToWorld); // 提取localToWorld的旋转部分(这是一个3x3矩阵) osg::Matrixd worldToLocal = osg::Matrixd::inverse(localToWorld); // 将物体的变换矩阵(objToWorld)转换到局部ENU坐标系中 osg::Matrixd objToENU = worldToLocal * matrix; // 注意:这里我们只关心旋转部分,因为平移部分在ENU坐标系中可能没有意义(除非需要局部位置) // 现在objToENU旋转部分表示从物体坐标系到ENU坐标系的旋转 osg::Quat quatENU = objToENU.getRotate(); // 然后,将四元数分解为欧拉角(注意顺序:航向、俯仰、横滚) // 由于欧拉角分解有顺序,我们可以使用: double heading, pitch, roll; // 注意:在ENU坐标系中,航向是绕Z轴(天)的旋转,俯仰是绕X轴(东)的旋转,横滚是绕Y轴(北)的旋转。 // 分解四元数到欧拉角(ZXY顺序?这取决于我们之前如何组合) // 但是,由于我们之前组合的顺序是:先航向(Z),再俯仰(X),再横滚(Y),所以分解顺序应该是反过来的:先横滚(Y),再俯仰(X),最后航向(Z)? // 实际上,欧拉角的分解顺序与组合顺序相反。我们可以使用: osg::Matrixd rot = osg::Matrixd(quatENU); // 使用osg::Matrixd::getRotate分解可能会得到四元数,但我们需要的是特定顺序的欧拉角。 // 另一种方法:直接使用矩阵元素计算(根据旋转矩阵的元素关系) // 注意:这需要知道旋转顺序,且当俯仰角为90度时会出现万向节死锁。 // 以下是一种常见的分解方式(假设旋转顺序为航向(Z)、俯仰(X)、横滚(Y)): // 先计算航向(绕Z轴) double sinHeading = -rot(0,1); // 假设旋转矩阵为R,则R(0,1) = -sin(heading) * cos(pitch) 等,具体公式需要推导 double cosHeading = rot(1,1); // 这里只是示意,实际公式如下: // 更标准的做法(按ZXY顺序分解): // 注意:我们组合旋转时是:quatENU = roll * pitch * heading (即先heading,再pitch,再roll,但乘法顺序是roll乘在pitch之前乘heading) // 所以分解时,先提取roll,再pitch,再heading。 // 由于欧拉角分解复杂且容易出错,建议使用四元数或矩阵进行运算,除非必须使用欧拉角。 // 如果必须使用欧拉角,我们可以使用以下方法(按ZXY顺序分解): // 参考:https://stackoverflow.com/questions/11514063/extract-yaw-pitch-and-roll-from-a-rotationmatrix // 但注意我们的坐标系是ENU(X:东,Y:北,Z:天),而标准分解可能是在不同坐标系下。 // 这里给出一个在ENU坐标系下,从旋转矩阵中分解航向、俯仰、横滚的方法(假设旋转顺序为航向(Z)、俯仰(X)、横滚(Y)): // 旋转矩阵R = R_y(roll) * R_x(pitch) * R_z(heading) // 即:先航向(绕Z),再俯仰(绕X),再横滚(绕Y) // 则: pitch = asin(rot(2,1)); // 注意矩阵索引:行,列,从0开始。R(2,1)即第三行第二列 // 然后计算heading和roll if (fabs(pitch) < (M_PI/2.0 - 1e-5)) { heading = atan2(-rot(0,1), rot(1,1)); roll = atan2(-rot(2,0), rot(2,2)); } else { // 万向节死锁,需要另外处理 // 当俯仰为90度或-90度时,航向和横滚重合 heading = atan2(rot(1,0), rot(0,0)); roll = 0.0; } // 注意:以上公式需要根据实际旋转顺序和矩阵定义调整。 由于欧拉角分解的复杂性,在osgEarth中,我们通常使用四元数或矩阵来表示方向,避免使用欧拉角。 4. 使用osgEarth的AnnotationUtils放置模型: osgEarth::AnnotationUtils::createNode() 函数可以接受一个可选的姿态参数(osg::MatrixTransform),用于设置模型的位置和方向。 示例:创建一个具有特定位置和方向的模型节点 osgEarth::GeoPoint position(mapNode->getMapSRS(), lon, lat, alt); osg::MatrixTransform* mt = new osg::MatrixTransform; mt->setMatrix( osg::Matrixd::translate(position.vec3d()) * osg::Matrixd::rotate(quat) ); mt->addChild(model); 注意:这里的位置是直接通过GeoPoint转换到世界坐标(ECEF)的,而旋转四元数quat是在地心坐标系下的旋转。如果我们的旋转是相对于ENU坐标系的,则需要先通过localToWorld矩阵将旋转转换到地心坐标系。 总结: 在osgEarth中,实体姿态(位置和方向)与旋转矩阵(变换矩阵)的转换涉及: - 位置:通过GeoPoint的toWorld和fromWorld进行地理坐标与地心坐标的转换。 - 方向:使用四元数或矩阵表示,注意坐标系(地心坐标系或局部ENU坐标系)的转换。 由于问题复杂,建议参考osgEarth的示例代码,如osgearth_viewer的方位指示器等。 §§相关问题§§ 1. osgEarth中如何将地理坐标(经纬度)转换为地心坐标系(ECEF)下的位置? 2. 在osgEarth中如何正确设置模型相对于地理北的航向? 3. 如何避免在欧拉角分解时出现的万向节死锁问题? 4. osgEarth中如何实现模型在两点之间的平滑旋转过渡?</think>在 osgEarth 中进行实体姿态与旋转矩阵之间的转换主要涉及以下核心操作,这些操作基于 osgEarth 的空间参考系转换能力和 OSG 的矩阵运算: ### 1. **姿态到旋转矩阵的转换** #### 1.1 基本转换(局部坐标系) ```cpp // 创建姿态(位置+方向) osg::Vec3d position(x, y, z); // 位置 osg::Quat quat(qx, qy, qz, qw); // 方向(四元数) // 构建变换矩阵(包含旋转和平移) osg::Matrixd transform; transform.setTrans(position); // 设置平移分量 transform.setRotate(quat); // 设置旋转分量(3x3旋转矩阵) ``` #### 1.2 地理空间转换(WGS84坐标系) ```cpp // 定义地理坐标点(经纬度高程) osgEarth::GeoPoint geoPos(mapSRS, lon, lat, alt); // 创建局部到世界的变换矩阵 osg::Matrixd localToWorld; geoPos.createLocalToWorld(localToWorld); // 包含地理坐标系的旋转和平移 // 组合实体自身旋转 osg::Matrixd entityRotation; entityRotation.makeRotate(osg::DegreesToRadians(heading), osg::Z_AXIS); // 航向旋转 // 最终变换矩阵 = 地理变换 × 实体旋转 osg::Matrixd finalTransform = localToWorld * entityRotation; ``` ### 2. **旋转矩阵到姿态的转换** #### 2.1 提取位置和方向 ```cpp // 从变换矩阵提取位置 osg::Vec3d worldPosition = transform.getTrans(); // 从变换矩阵提取旋转四元数 osg::Quat worldOrientation = transform.getRotate(); ``` #### 2.2 转换为地理姿态 ```cpp // 将世界坐标转换为地理坐标 osgEarth::GeoPoint geoPos; geoPos.fromWorld(mapNode->getMapSRS(), worldPosition); // 提取经纬度高程 double lon = geoPos.x(); double lat = geoPos.y(); double alt = geoPos.z(); // 从旋转矩阵提取航向角(绕Z轴) osg::Matrixd rot = transform.getRotate(); double heading = atan2(rot(1,0), rot(0,0)); // 弧度值 heading = osg::RadiansToDegrees(heading); // 转换为角度 ``` ### 3. **关键应用场景** #### 3.1 模型放置 ```cpp osg::MatrixTransform* mt = new osg::MatrixTransform; mt->setMatrix(finalTransform); // 设置变换矩阵 mt->addChild(modelNode); // 添加模型 mapNode->addChild(mt); // 添加到场景 ``` #### 3.2 相机姿态控制 ```cpp osg::Matrixd viewMatrix; viewMatrix.makeLookAt(eye, center, up); // 创建视图矩阵 viewer.getCamera()->setViewMatrix(viewMatrix); ``` #### 3.3 动态姿态更新 ```cpp // 每帧更新实体姿态 void updateCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) { osg::MatrixTransform* mt = static_cast<osg::MatrixTransform*>(node); osg::Matrixd matrix = mt->getMatrix(); // 修改旋转分量(示例:绕Y轴旋转) matrix.preMult(osg::Matrixd::rotate(deltaAngle, osg::Y_AXIS)); mt->setMatrix(matrix); } ``` ### 4. **重要注意事项** 1. **坐标系关系**: - 局部坐标系:物体自身坐标系(通常Y轴向前,Z轴向上) - 世界坐标系:地心坐标系(ECEF) - 地理坐标系:WGS84(经度、纬度、高程) 2. **矩阵乘法顺序**: - 使用 `preMult()` 表示在世界坐标系中变换 - 使用 `postMult()` 表示在局部坐标系中变换 - 地理变换总是最先应用:`World = Geographic × Local` 3. **性能优化**: ```cpp // 优先使用四元数运算(比矩阵效率高约30%) osg::Quat newOrientation = oldOrientation * osg::Quat(delta, axis); ``` ### 典型转换流程 ```mermaid graph LR A[地理坐标<br>经/纬/高] -->|createLocalToWorld| B[世界矩阵] C[实体姿态<br>位置+四元数] -->|setTrans+setRotate| D[局部变换矩阵] B -->|矩阵乘法| E[最终世界矩阵] D -->|矩阵乘法| E E -->|getTrans| F[世界坐标] E -->|getRotate| G[世界方向] F -->|fromWorld| H[提取地理坐标] G -->|矩阵分解| I[提取欧拉角] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一梦、んんん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值