/*
***本项目包含第三天和第四天的大作业
***第三天 大作业内容
模拟太阳系,绘制十个球体,包括太阳、八大行星和月球,为八大行星和月
球绘制公转轨道线。
要求:
1) 球体、轨道线用使用图元绘制,不能直接调用 OSG 提供的球、线图
形方法;
2) 对于无法求出球体顶点坐标的学习者,可以参照《绘制球体》文档
3) 实现时尽可能满足 C++封装的特性,给出包括公转半径、球体半径、
公转自转速度、纹理路径等基本信息,输出星球的节点指针。
4) 基于封装的生成球体类或函数,在木星和火星之间创建一百个球体
半径大小随机的小行星,形成小行星带,无须尾迹和纹理,但要求小行星分
布均匀,公转速度一直,球体半径不同。
***第四天 大作业内容
基于上次的大作业,给各个星球贴上纹理,修改大作业中球体轨道颜色、线
宽、线型,此次作业无需动态修改图元属性,只需展示图元的纹理和颜色等属性
即可。
要求:
1) 纹理坐标需要手动设置,不能调用 OSG 提供的自动生成纹理坐标功能;
*/
/**
@brief : 函数:创建轨道线
@param : [in] center 中心点
@param : [in] radius 半径
@param : [in] segments 拆分线数
@return:轨迹线 osg::ref_ptr<osg::Geometry>
*/
osg::ref_ptr<osg::Geometry> createOrbit(const osg::Vec3& center, float radius, int segments, const osg::Vec4& color) {
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array();
for (int i = 0; i < segments; ++i) {
float angle = 2 * osg::PI * i / segments;
float x = center.x() + radius * cos(angle);
float y = center.y() + radius * sin(angle);
float z = center.z();
vertices->push_back(osg::Vec3(x, y, z));
}
geometry->setVertexArray(vertices);
geometry->addPrimitiveSet(new osg::DrawArrays(GL_LINE_LOOP, 0, vertices->size()));
osg::ref_ptr<osg::StateSet> stateSet = geometry->getOrCreateStateSet();
osg::ref_ptr<osg::LineWidth> lineWidth = new osg::LineWidth(2.0f);
stateSet->setAttributeAndModes(lineWidth, osg::StateAttribute::ON);
stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
osg::ref_ptr<osg::Material> material = new osg::Material;
material->setAmbient(osg::Material::FRONT, color);
material->setDiffuse(osg::Material::FRONT, color);
stateSet->setAttribute(material);
stateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
geometry->setName("orbitGeometry"); // 确保名称正确设置
return geometry.release();
}
/**
@brief : 创建一个球的几何节点
@param : [in] dRadius 球的半径
@param : [in] iHint 每180/iHint度设置一个点,默认18
@return: 一个球的osg::Geometry*
*/
osg::Geometry* createSphereGeom(const osg::Vec3& center, double dRadius = 1.0, int iHint = 18)
{
osg::ref_ptr<osg::Geometry> rpGeom = new osg::Geometry;
osg::ref_ptr<osg::Vec3Array> rpVertexes = new osg::Vec3Array;//顶点数组
osg::ref_ptr<osg::Vec3Array> rpNormal = new osg::Vec3Array;//法线数组
osg::ref_ptr<osg::Vec2Array> rpTexCoord = new osg::Vec2Array;//纹理数组
for (int i = 0; i <= iHint; i++)//从上到下添加点,有等于号是为了之后贴纹理时可以和图片的点一一对应
{
for (int j = 0; j <= iHint * 2; j++)//逆时针添加点
{
osg::Vec3 vec3VertexT(
center.x() + sin(osg::PI*i / iHint)*cos(osg::PI*j / iHint),
center.y() + sin(osg::PI*i / iHint)*sin(osg::PI*j / iHint),
center.z() + cos(osg::PI*i / iHint));//球面坐标公式
rpVertexes->push_back(vec3VertexT * dRadius);//添加顶点
rpNormal->push_back(vec3VertexT);//添加法线
rpTexCoord->push_back(osg::Vec2(double(j) / 2.0 / iHint
, 1 - double(i) / iHint));//添加纹理坐标
}
}
osg::ref_ptr<osg::Vec4Array> rpColors = new osg::Vec4Array;
rpColors->push_back(osg::Vec4(1.0, 1.0, 1.0, 1.0));
rpGeom->setVertexArray(rpVertexes);
rpGeom->setNormalArray(rpNormal);
rpGeom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
rpGeom->setColorArray(rpColors);
rpGeom->setColorBinding(osg::Geometry::BIND_OVERALL);
rpGeom->setTexCoordArray(0, rpTexCoord);
//添加图元
osg::ref_ptr<osg::DrawElementsUInt> rpFace = new osg::DrawElementsUInt(osg::PrimitiveSet::TRIANGLE_STRIP);
for (int i = 0; i < iHint; i++) {
for (int j = 0; j <= iHint * 2; j++) {
rpFace->push_back(i*(iHint * 2 + 1) + j);
rpFace->push_back((i + 1)*(iHint * 2 + 1) + j);
}
}
rpGeom->addPrimitiveSet(rpFace);
return rpGeom.release();
}
/**
@brief : 给球贴纹理
@param : [in/out] pNode 球的节点
@return:
*/
void setTex(osg::Node* pNode, std::string path)
{
osg::ref_ptr<osg::Image> rpImage = osgDB::readImageFile(path);
osg::ref_ptr<osg::Texture2D> rpTexture = new osg::Texture2D();
rpTexture->setImage(rpImage);
rpTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);//设置S方向的环绕模式
rpTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);//设置R方向的环绕模式
osg::ref_ptr<osg::StateSet> pState = pNode->getOrCreateStateSet();
pState->setTextureAttributeAndModes(0, rpTexture, osg::StateAttribute::ON);
//关闭光照确保纹理可见
pState->setMode(GL_LIGHTING, osg::StateAttribute::OFF); // 禁用光照,以便纹理可见
}
/**
@brief : 创建包含轨道、轨道颜色、星球的Group
@param : [in] name 名字
@param : [in] texturePath 纹理路径
@param : [in] place 相对于太阳的位置
@param : [in] radius 半径
@param : [in] cenetr 轨道中心
@return: osg::Group *
note:过多参数 用 结构体
*/
osg::Group * makePlanetGroup(std::string name, std::string texturePath, osg::Vec3 place, double radius, osg::Vec3 center = osg::Vec3(0, 0, 0))
{
osg::ref_ptr<osg::MatrixTransform> planetTransform = new osg::MatrixTransform;
planetTransform->setMatrix(osg::Matrix::translate(place));
osg::ref_ptr<osg::Geode> planetGeode = new osg::Geode;
planetGeode->setName(name);
planetTransform->setName(name);
// 创建星球球体
osg::ref_ptr<osg::Geometry> planetGeometry = createSphereGeom(osg::Vec3(0, 0, 0), radius, 45);
planetGeometry->setName(name);
planetGeode->addChild(planetGeometry);
//当是地球时,添加月球
if (name == "earth")
{
planetTransform->addChild(makePlanetGroup("moon", R"(E:\xx\newOSG\Data\Star\moon.jpg)", osg::Vec3(6, 0, 0), 0.3));
}
// 设置星球纹理
setTex(planetGeode, texturePath); // 在球上贴纹理
planetTransform->addChild(planetGeode);
// 轨道设置
osg::ref_ptr<osg::Geode> orbitGeode = new osg::Geode;
orbitGeode->setName(name.append("orbitGeometry"));
osg::ref_ptr<osg::Geometry> orbitGeometry = createOrbit(center, place.x() - center.x() < 0 ? center.x() - place.x() : place.x() - center.x(), 50, osg::Vec4(0.5f, 0.7f, 0.8f, 1.f));
orbitGeode->addChild(orbitGeometry);
// 默认不显示
osg::ref_ptr<osg::Switch> orbitControl = new osg::Switch();
orbitControl->setAllChildrenOff();
orbitControl->setName(name.append("0rbit"));
orbitControl->addChild(orbitGeode);
// 添加到星球组节点
osg::ref_ptr<osg::Group> planetGroup = new osg::Group;
planetGroup->setName(name);
planetGroup->addChild(planetTransform);
planetGroup->addChild(orbitControl);
return planetGroup.release();
}
/**
@brief : 在离圆心(太阳中心点,即(0,0,0)点)nearadius到farradius 随机生成100个陨石
@param : [in] nearadius 近轨道
@param : [in] farradius 远轨道
@return: 组节点
*/
osg::ref_ptr<osg::Group> makeStonesBetween(float nearadius, float farradius)
{
// 创建一个组来容纳所有的陨石
osg::ref_ptr<osg::Group> stonesGroup = new osg::Group();
// 设置随机数种子
srand(static_cast<unsigned int>(time(nullptr)));
// 生成100个陨石
for (int i = 0; i < 100; ++i) {
// 随机生成石头的半径
float radius = static_cast<float>(nearadius + (farradius - nearadius) * (rand() / static_cast<float>(RAND_MAX)));
// 随机生成石头的角度
float angle = static_cast<float>(rand() / static_cast<float>(RAND_MAX) * 2 * osg::PI);
// 计算石头的x和y坐标
float x = radius * cos(angle);
float y = radius * sin(angle);
float z = 0.0f; // z坐标固定为0
std::random_device rd; // 用于获取随机数种子
std::mt19937 gen(rd()); // 伪随机数生成器
std::uniform_real_distribution<> dis(0.1, 0.5); // 定义分布范围
float radiusst = dis(gen);// 生成随机浮点数
// 将陨石节点添加到组中
stonesGroup->addChild(makePlanetGroup("stones" + std::to_string(i), R"(E:\xx\newOSG\Data\Star\moon.jpg)", osg::Vec3(x, y, z), radiusst));
}
return stonesGroup;
}
int main(int argc, char** argv)
{
osg::ref_ptr<osgViewer::Viewer> rpViewer = new osgViewer::Viewer();
// 根节点
osg::ref_ptr<osg::Group> rpRoot = new osg::Group();
//新建太阳系
osg::ref_ptr<osg::Group> sunGroup = new osg::Group;
sunGroup->setName("sun");
// 新建太阳
osg::ref_ptr<osg::Geode> sun = new osg::Geode;
sun->setName("sun");
sun->addChild(createSphereGeom(osg::Vec3(0, 0, 0), 10., 45));
setTex(sun, R"(E:\xx\newOSG\Data\Star\sun.jpg)");//在球上贴纹理
//创建太阳的matrixTransform
osg::ref_ptr<osg::MatrixTransform> earthTransform = new osg::MatrixTransform();
earthTransform->setName("sun");
earthTransform->addChild(sun);
sunGroup->addChild(earthTransform);
// 将行星的变换节点添加到太阳节点中
sunGroup->addChild(makePlanetGroup("venus", R"(E:\xx\newOSG\Data\Star\venus.jpg)", osg::Vec3(25, 0, 0), 2.));
sunGroup->addChild(makePlanetGroup("mercury", R"(E:\xx\newOSG\Data\Star\mercury.jpg)", osg::Vec3(15, 0, 0), 0.6));
sunGroup->addChild(makePlanetGroup("earth", R"(E:\xx\newOSG\Data\Star\earth.jpg)", osg::Vec3(45, 0, 0), 4));
sunGroup->addChild(makePlanetGroup("mars", R"(E:\xx\newOSG\Data\Star\mars.jpg)", osg::Vec3(60, 0, 0), 1.3));
sunGroup->addChild(makePlanetGroup("jupiter", R"(E:\xx\newOSG\Data\Star\jupiter.jpg)", osg::Vec3(80, 0, 0), 1.5));
sunGroup->addChild(makePlanetGroup("saturn", R"(E:\xx\newOSG\Data\Star\saturn.jpg)", osg::Vec3(90, 0, 0), 1.6));
sunGroup->addChild(makePlanetGroup("uranus", R"(E:\xx\newOSG\Data\Star\uranus.jpg)", osg::Vec3(96, 0, 0), 1.7));
sunGroup->addChild(makePlanetGroup("neptune", R"(E:\xx\newOSG\Data\Star\neptune.jpg)", osg::Vec3(107, 0, 0), 1.8));
sunGroup->addChild(makeStonesBetween(65.f, 75.f));
//将太阳系节点添加到根节点
rpRoot->addChild(sunGroup);
rpViewer->setSceneData(rpRoot);
rpViewer->addEventHandler(new osgGA::StateSetManipulator(rpViewer->getCamera()->getOrCreateStateSet()));//添加w事件处理
rpViewer->addEventHandler(new osgViewer::StatsHandler);//添加s事件处理
rpViewer->run();
return 0;
}
最后别忘记替换贴图路径哦!