OpenSceneGraph实现的NeHe OpenGL教程 - 第二十四课

  • 简介

NeHe教程在这节课中向我们介绍了如何读取显卡支持的OpenGL的扩展,如何使用Targa(TGA)图像文件作为纹理,以及如何利用OpenGL的剪裁区域来滚动屏幕文字。osg支持tga格式的文件读取,因此不需要像NeHe课程那样解析TGA格式,有兴趣的读者可以查看osgPlugins库中的ReaderWriterTGA.cpp文件了解osg是如何读取TGA图片格式的。

用纹理贴图来创建字体在第十七课中已经使用过了,过程相对比较繁琐,本课的主要目的是学习如何查看OpenGL的扩展,为了简便,本课使用osgText来显示字体。

  • 实现

本课中NeHe使用的是坐标原点在左上角的坐标系统,但是在osg中设置同样的投影坐标系会导致文字反转,在osg论坛中对此也有描述:osgText::Text and screen

按照帖子中修改之后发现字体消失,希望知道的读者指点一下。为此本课还是采用坐标原点在左下角的坐标系统,定义了TX和TY宏以便于使用NeHe中的位置数据

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #define TX(x) (x)  
  2. #define TY(y) (480-(y))  
首先定义创建字体的函数:(字体位置和颜色以及文字内容)

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. osg::Group* createText(int xPos, int yPos, osg::Vec4 colors, const char *contents)  
  2. {  
  3.     osgText::Text *text = new osgText::Text;  
  4.     text->setText(contents);  
  5.     text->setFont("Fonts/Arial.ttf");  
  6.     text->setCharacterSize(15.0);  
  7.     text->setColor(colors);  
  8.   
  9.     osg::Geode *textGeode = new osg::Geode;  
  10.     textGeode->addDrawable(text);  
  11.   
  12.     osg::MatrixTransform *mt = new osg::MatrixTransform;  
  13.     mt->setMatrix(osg::Matrix::translate(xPos, yPos, 0.0));  
  14.   
  15.     mt->addChild(textGeode);  
  16.   
  17.     return mt;  
  18. }  
之后创建边框线:
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. osg::Group * createDisplayBorder()  

为了获取显卡的扩展,需要我们在GraphicContext创建好之后获取,因此在Viewer视景器Realize的时候来获取是一个不错的时机。本课通过设置Realize的一个operation来获取扩展信息。Operation是一个自定义的操作,在osg中可以在某个过程完成之后定义一些操作。只需要重载Operation中的()操作符即可实现自定义操作:

在osg中有一个类用来获取扩展的信息:osg::GL2Extensions可以查看当前显卡支持的各种扩展以及支持的OpenGL版本,但是在它里面未找到显卡供应商和显卡型号的接口,我们可以用OpenGL的glGetString来获取。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. virtual void operator () (osg::GraphicsContext* gc)  
  2. {  
  3.  if (_isInitialized)  
  4.  {  
  5.   return;  
  6.  }  
  7.   
  8.  OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);  
  9.   
  10.  unsigned int contextID = gc->getState()->getContextID();  
  11.  osg::GL2Extensions* gl2ext = osg::GL2Extensions::Get(contextID,true);  
  12.  if( gl2ext )  
  13.  {      
  14.   char *render = (char*)glGetString(GL_RENDERER);  
  15.   char *vendor =  (char*)glGetString(GL_VENDOR);  
  16.   char *version = (char*)glGetString(GL_VERSION);  
  17.   
  18.                        ...  
  19.                 }  
  20.                ...  
  21.         }  
接着我们需要定义裁剪范围,在osg中osg::Scissor封装了OpenGL中glScissor的功能,它是一个StateAttribute,通过给节点设置StateAttribute进一步控制节点在视口

中的显示范围:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. osg::Group *scissorGroup = new osg::Group;  
  2. g_scissorGroup = scissorGroup;  
  3. osg::Scissor *scissor = new osg::Scissor;  
  4. scissor->setScissor(1, TY(417), g_width-2 , 289);  
  5. g_scissorGroup->getOrCreateStateSet()->setAttributeAndModes(scissor);  

通过键盘上下键操作的部分如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)  
  2. {  
  3.     if((scroll<32*(maxtokens-9)))  
  4.     {  
  5.         scroll += 2;  
  6.           
  7.         for (unsigned i = 0, j = 0; i < g_scissorGroup->getNumChildren(); i +=2)  
  8.         {  
  9.             osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(g_scissorGroup->getChild(i));  
  10.             if (!mt)  
  11.                 return false;  
  12.             //扩展的序号:如1 2 3 等  
  13.             mt->setMatrix(osg::Matrix::translate(TX(0), TY(115+(j*32)-scroll), 0.0));      
  14.             osg::MatrixTransform *mt2 = dynamic_cast<osg::MatrixTransform*>(g_scissorGroup->getChild(i+1));  
  15.             if (!mt2)  
  16.                 return false;  
  17.             //扩展的内容 如GL_ARB_clear_buffer_object等  
  18.             mt2->setMatrix(osg::Matrix::translate(TX(50), TY(115+(j*32)-scroll), 0.0));    
  19.             ++j;  
  20.         }  
  21.   
  22.     }  
  23. }  

编译运行程序:


附:本课源码(源码中可能存在错误和不足,仅供参考)

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include "../osgNeHe.h"  
  2.   
  3. #include <QtCore/QTimer>  
  4. #include <QtGui/QApplication>  
  5. #include <QtGui/QVBoxLayout>  
  6.   
  7. #include <osgViewer/Viewer>  
  8. #include <osgDB/ReadFile>  
  9. #include <osgQt/GraphicsWindowQt>  
  10.   
  11. #include <osg/MatrixTransform>  
  12.   
  13. #include <osg/Geode>  
  14. #include <osgText/Text>  
  15.   
  16. #include <osg/Scissor>  
  17.   
  18. #include <osg/GraphicsThread>  
  19.   
  20.   
  21. #define TX(x) (x)  
  22. #define TY(y) (480.0-(y))  
  23.   
  24. char *glExtensionString = NULL;  
  25.   
  26. int         scroll;  
  27. int         maxtokens;  
  28. int g_height = 480;  
  29. int g_width = 640;  
  30. osg::Group *g_scissorGroup;  
  31.   
  32.   
  33. //  
  34. //在指定位置显示文本  
  35.   
  36. osg::Group* createText(int xPos, int yPos, osg::Vec4 colors, const char *contents)  
  37. {  
  38.     osgText::Text *text = new osgText::Text;  
  39.     text->setText(contents);  
  40.     text->setFont("Fonts/Arial.ttf");  
  41.     text->setCharacterSize(15.0);  
  42.     text->setColor(colors);  
  43.   
  44.     osg::Geode *textGeode = new osg::Geode;  
  45.     textGeode->addDrawable(text);  
  46.   
  47.     osg::MatrixTransform *mt = new osg::MatrixTransform;  
  48.     mt->setMatrix(osg::Matrix::translate(xPos, yPos, 0.0));  
  49.   
  50.     mt->addChild(textGeode);  
  51.   
  52.     return mt;  
  53. }  
  54.   
  55. //  
  56. osg::Group * createDisplayBorder()  
  57. {  
  58.     //  
  59.     osg::Group *borderGroup = new osg::Group;  
  60.     osg::Geode* borderGeode1 = new osg::Geode;  
  61.     osg::Geometry *borderGeometry1 = new osg::Geometry;  
  62.   
  63.     osg::Vec2Array *verticesArray1 = new osg::Vec2Array;  
  64.     verticesArray1->push_back(osg::Vec2(639,TY(417)));  
  65.     verticesArray1->push_back(osg::Vec2(  0,TY(417)));  
  66.     verticesArray1->push_back(osg::Vec2(  0,TY(480)));  
  67.     verticesArray1->push_back(osg::Vec2(639,TY(480)));  
  68.     verticesArray1->push_back(osg::Vec2(639,TY(128)));  
  69.     borderGeometry1->setVertexArray(verticesArray1);  
  70.     borderGeometry1->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, 5));  
  71.     osg::Vec3Array *colorsArray1 = new osg::Vec3Array;  
  72.     colorsArray1->push_back(osg::Vec3(1.0f,1.0f,1.0f));  
  73.     borderGeometry1->setColorArray(colorsArray1, osg::Array::BIND_OVERALL);  
  74.     borderGeode1->addDrawable(borderGeometry1);  
  75.   
  76.     borderGroup->addChild(borderGeode1);  
  77.       
  78.   
  79.     osg::Geode* borderGeode2 = new osg::Geode;  
  80.     osg::Geometry *borderGeometry2 = new osg::Geometry;  
  81.   
  82.     osg::Vec2Array *verticesArray2 = new osg::Vec2Array;  
  83.     verticesArray2->push_back(osg::Vec2(  0,TY(128)));  
  84.     verticesArray2->push_back(osg::Vec2(639,TY(128)));  
  85.     verticesArray2->push_back(osg::Vec2(639,  TY(1)));  
  86.     verticesArray2->push_back(osg::Vec2(  0,  TY(1)));  
  87.     verticesArray2->push_back(osg::Vec2(  0,TY(417)));  
  88.     borderGeometry2->setVertexArray(verticesArray2);  
  89.     borderGeometry2->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, 5));  
  90.     osg::Vec3Array *colorsArray2 = new osg::Vec3Array;  
  91.     colorsArray2->push_back(osg::Vec3(1.0f,1.0f,1.0f));  
  92.     borderGeometry2->setColorArray(colorsArray2, osg::Array::BIND_OVERALL);  
  93.     borderGeode2->addDrawable(borderGeometry2);  
  94.   
  95.     borderGroup->addChild(borderGeode2);  
  96.     return borderGroup;  
  97. }  
  98.   
  99.   
  100. //  
  101. //获取OpenGL扩展  
  102. class GetOpenGLExtensionsOperation: public osg::GraphicsOperation  
  103. {  
  104. public:  
  105.   
  106.     GetOpenGLExtensionsOperation(const std::string& name, bool keep, osg::Group *root) :   
  107.       osg::GraphicsOperation("TestSupportOperation",false), _isInitialized(false), _root(root){ }  
  108.   
  109.       virtual void operator () (osg::GraphicsContext* gc)  
  110.       {  
  111.           if (_isInitialized)  
  112.           {  
  113.               return;  
  114.           }  
  115.   
  116.           OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);  
  117.   
  118.           unsigned int contextID = gc->getState()->getContextID();  
  119.           osg::GL2Extensions* gl2ext = osg::GL2Extensions::Get(contextID,true);  
  120.           if( gl2ext )  
  121.           {     
  122.               char *render = (char*)glGetString(GL_RENDERER);  
  123.               char *vendor =  (char*)glGetString(GL_VENDOR);  
  124.               char *version = (char*)glGetString(GL_VERSION);  
  125.   
  126.               _root->addChild(createText(TX(200), TY(16), osg::Vec4(1.0f,0.7f,0.4f, 1.0f), render));  
  127.               _root->addChild(createText(TX(200), TY(48), osg::Vec4(1.0f,0.7f,0.4f, 1.0f), vendor));  
  128.               _root->addChild(createText(TX(200), TY(80), osg::Vec4(1.0f,0.7f,0.4f, 1.0f), version));  
  129.   
  130.               glExtensionString = (char *)malloc(strlen((char *)glGetString(GL_EXTENSIONS))+1);  
  131.               strcpy (glExtensionString,(char *)glGetString(GL_EXTENSIONS));  
  132.   
  133.               osg::Group *scissorGroup = new osg::Group;  
  134.               g_scissorGroup = scissorGroup;  
  135.               osg::Scissor *scissor = new osg::Scissor;  
  136.               scissor->setScissor(1, TY(417), g_width-2 , 289);  
  137.               g_scissorGroup->getOrCreateStateSet()->setAttributeAndModes(scissor);  
  138.                 
  139.               _root->addChild(g_scissorGroup);  
  140.   
  141.               char *token;  
  142.               int cnt = 0;  
  143.               token=strtok(glExtensionString," ");                                
  144.               while(token!=NULL)                                      
  145.               {  
  146.                   cnt++;      
  147.                   if (cnt>maxtokens)   
  148.                   {  
  149.                       maxtokens=cnt;  
  150.                   }  
  151.                   
  152.   
  153.                   std::stringstream os;  
  154.                   std::string str;  
  155.                   os.precision(2);  
  156.                   os << std::fixed  << cnt;  
  157.                   str = os.str();  
  158.   
  159.                   scissorGroup->addChild(createText(TX(0), TY(115+(cnt*32)-scroll), osg::Vec4(0.5f, 1.0f, 0.5f, 1.0f), str.c_str()));  
  160.                   scissorGroup->addChild(createText(TX(50), TY(115+(cnt*32)-scroll), osg::Vec4(1.0f, 1.0f, 0.5f, 1.0f), token));  
  161.   
  162.                   token=strtok(NULL," ");  
  163.               }  
  164.   
  165.           }  
  166.           _isInitialized = true;  
  167.       }  
  168.   
  169.       OpenThreads::Mutex  _mutex;  
  170.       bool _isInitialized;  
  171.       osg::Group *_root;  
  172. };  
  173.   
  174. //  
  175.   
  176. class SceneEventHandler : public osgGA::GUIEventHandler  
  177. {  
  178. public:  
  179.     SceneEventHandler(){}  
  180.   
  181.     virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)  
  182.     {  
  183.         osgViewer::Viewer *viewer = dynamic_cast<osgViewer::Viewer*>(&aa);  
  184.         if (!viewer)  
  185.             return false;  
  186.         osg::Group *root = dynamic_cast<osg::Group*>(viewer->getSceneData());  
  187.         if (!root)  
  188.             return false;  
  189.         if (ea.getHandled())   
  190.             return false;  
  191.   
  192.         switch(ea.getEventType())  
  193.         {  
  194.         case (osgGA::GUIEventAdapter::KEYDOWN):  
  195.             {  
  196.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)  
  197.                 {  
  198.                     if((scroll<32*(maxtokens-9)))  
  199.                     {  
  200.                         scroll += 2;  
  201.                           
  202.                         for (unsigned i = 0, j = 0; i < g_scissorGroup->getNumChildren(); i +=2)  
  203.                         {  
  204.                             osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(g_scissorGroup->getChild(i));  
  205.                             if (!mt)  
  206.                                 return false;  
  207.                             //扩展的序号:如1 2 3 等  
  208.                             mt->setMatrix(osg::Matrix::translate(TX(0), TY(115+(j*32)-scroll), 0.0));      
  209.                             osg::MatrixTransform *mt2 = dynamic_cast<osg::MatrixTransform*>(g_scissorGroup->getChild(i+1));  
  210.                             if (!mt2)  
  211.                                 return false;  
  212.                             //扩展的内容 如GL_ARB_clear_buffer_object等  
  213.                             mt2->setMatrix(osg::Matrix::translate(TX(50), TY(115+(j*32)-scroll), 0.0));    
  214.                             ++j;  
  215.                         }  
  216.   
  217.                     }  
  218.                 }  
  219.                 if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)  
  220.                 {  
  221.                     if (scroll >= 0)  
  222.                     {  
  223.                         scroll -= 2;  
  224.   
  225.                         for (unsigned i = 0, j = 0; i < g_scissorGroup->getNumChildren(); i +=2)  
  226.                         {  
  227.                             osg::MatrixTransform *mt = dynamic_cast<osg::MatrixTransform*>(g_scissorGroup->getChild(i));  
  228.                             if (!mt)  
  229.                                 return false;  
  230.                             mt->setMatrix(osg::Matrix::translate(TX(0), TY(115+(j*32)-scroll), 0.0));      
  231.   
  232.                             osg::MatrixTransform *mt2 = dynamic_cast<osg::MatrixTransform*>(g_scissorGroup->getChild(i+1));  
  233.                             if (!mt2)  
  234.                                 return false;  
  235.                             mt2->setMatrix(osg::Matrix::translate(TX(50), TY(115+(j*32)-scroll), 0.0));    
  236.                             ++j;  
  237.                         }  
  238.                     }  
  239.                 }  
  240.             }     
  241.         case (osgGA::GUIEventAdapter::RESIZE):  
  242.             {  
  243.                 g_height = ea.getWindowHeight();  
  244.                 g_width = ea.getWindowWidth();  
  245.             }  
  246.         defaultbreak;  
  247.         }  
  248.         return false;  
  249.     }  
  250. };  
  251. //  
  252.   
  253. class ViewerWidget : public QWidget, public osgViewer::Viewer  
  254. {  
  255. public:  
  256.     ViewerWidget(osg::Node *scene = NULL)  
  257.     {  
  258.         QWidget* renderWidget = getRenderWidget( createGraphicsWindow(0,0,640,480), scene);  
  259.   
  260.         QVBoxLayout* layout = new QVBoxLayout;  
  261.         layout->addWidget(renderWidget);  
  262.         layout->setContentsMargins(0, 0, 0, 1);  
  263.         setLayout( layout );  
  264.   
  265.         connect( &_timer, SIGNAL(timeout()), this, SLOT(update()) );  
  266.         _timer.start( 10 );  
  267.     }  
  268.   
  269.     QWidget* getRenderWidget( osgQt::GraphicsWindowQt* gw, osg::Node* scene )  
  270.     {  
  271.         osg::Camera* camera = this->getCamera();  
  272.         camera->setGraphicsContext( gw );  
  273.   
  274.         const osg::GraphicsContext::Traits* traits = gw->getTraits();  
  275.   
  276.         camera->setClearColor( osg::Vec4(0.0, 0.0, 0.0, 1.0) );  
  277.         camera->setViewport( new osg::Viewport(0, 0, traits->width, traits->height) );  
  278.         camera->setProjectionMatrixAsOrtho(0.0, 640.0, 0.0, 480.0, -1.0, 1.0);  
  279.         camera->setViewMatrixAsLookAt(osg::Vec3d(0, 0, 1), osg::Vec3d(0, 0, 0), osg::Vec3d(0, 1, 0));  
  280.   
  281.         setRealizeOperation(new GetOpenGLExtensionsOperation("OpenGLExtension"false, scene->asGroup()));  
  282.         addEventHandler(new SceneEventHandler());  
  283.         this->setSceneData( scene );  
  284.   
  285.         return gw->getGLWidget();  
  286.     }  
  287.   
  288.     osgQt::GraphicsWindowQt* createGraphicsWindow( int x, int y, int w, int h, const std::string& name=""bool windowDecoration=false )  
  289.     {  
  290.         osg::DisplaySettings* ds = osg::DisplaySettings::instance().get();  
  291.         osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;  
  292.         traits->windowName = name;  
  293.         traits->windowDecoration = windowDecoration;  
  294.         traits->x = x;  
  295.         traits->y = y;  
  296.         traits->width = w;  
  297.         traits->height = h;  
  298.         traits->doubleBuffer = true;  
  299.         traits->alpha = ds->getMinimumNumAlphaBits();  
  300.         traits->stencil = ds->getMinimumNumStencilBits();  
  301.         traits->sampleBuffers = ds->getMultiSamples();  
  302.         traits->samples = ds->getNumMultiSamples();  
  303.   
  304.         return new osgQt::GraphicsWindowQt(traits.get());  
  305.     }  
  306.   
  307.     virtual void paintEvent( QPaintEvent* event )  
  308.     {   
  309.         frame();   
  310.     }  
  311.   
  312. protected:  
  313.   
  314.     QTimer _timer;  
  315. };  
  316.   
  317.   
  318.   
  319. osg::Node*  buildScene()  
  320. {  
  321.     osg::Group *root = new osg::Group;  
  322.   
  323.     root->addChild(createText(TX(60), TY(16), osg::Vec4(1.0f,0.5f,0.5f, 1.0f), "Renderer"));  
  324.     root->addChild(createText(TX(80), TY(48), osg::Vec4(1.0f,0.5f,0.5f, 1.0f), "Vendor"));  
  325.     root->addChild(createText(TX(70), TY(80), osg::Vec4(1.0f,0.5f,0.5f, 1.0f), "Version"));  
  326.     root->addChild(createText(TX(192), TY(432), osg::Vec4(0.5f,0.5f,1.0f, 1.0f), "NeHe Productions"));  
  327.       
  328.     root->addChild(createDisplayBorder());  
  329.     return root;  
  330. }  
  331.   
  332.   
  333.   
  334. int main( int argc, char** argv )  
  335. {  
  336.     QApplication app(argc, argv);  
  337.     ViewerWidget* viewWidget = new ViewerWidget(buildScene());  
  338.     viewWidget->setGeometry( 100, 100, 640, 480 );  
  339.     viewWidget->show();  
  340.     return app.exec();  
  341. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&程设计(包含运行文档+数据库+前后端代码),Java实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值