最开始设计这个编辑器时,其中一个要求就是能在运行过程中,通过UI来更新各对象,这样我们就能明确每个Ogre对象更新其属性影响的渲染效果.比如点光源,方向光源,聚光灯各属性与效果,深度测试开启与关闭,深度比较方式影响的效果等等.如下先看效果图:
这个位置没有用上一篇天龙的场景,主要是图片大小限制,场景复杂后,生成的gif图片太大.
这个功能当时我主界面完成后,就准备做的,但是当时一时想不到好的方案,如果针对每个Ogre属性来生成UI,然后关联每个对象,再想想后续如果要修改其中的字段属性或是位置,这个工作量让我不得不先想一个方案能够避免这些问题.在完成第二篇天龙场景的加载时,顺便在空闲时间想出现在的这种解决方法,不说这方法好,但是工作量少,简洁,易修改,避免很多BUG.灵活性高,只有二个Ogre之间有关联,就可以显示在一起.如上面的Entity,他显示出他父节点SceneNode,以及他的父类MovableObject的各属性.
这个方案简单来说,分成三个部分,一个部分主要是XML文件,XML文件包含每个Ogre对象要关联的对象,字段,关联字段等.第二个部分是自动生成控件界面,根据第一部分的信息来生成各个界面.第三部分是根据Ogre对象更新各个控件,以及更新控件上的值更新后反馈给对应Ogre对象.如下是三个部分代码图,VS中C++代码生成的代码关联图中,聚合关系不能显示出来,所以一些聚合信息在上面没有显示出来.
XML文件与基本类
这个部分主要有二方面,一方面是XML文件保存的信息,包含每个Ogre对象关联的对象,每个对象更新的字段,字段的项选择,如下图:
然后就是如第一张图中的类OgreStyle,我们可以看到,这个类三个部分都有引用到,也是我们的这解决方法的核心类,这个类是一个树型结构,这个结构在这主要有二个好处,一是容易添加在树型控件里.二是可以得到父节点信息与子节点信息.其中我们设计在删除节点时,所有子节点全删除.如下是OgreStyle代码.
namespace Ogre3DX { typedef std::vector<string> VectorString; class OgreStyle; typedef std::vector<OgreStyle*> OgreStyleVector; class OgreStyle { public: //ResourceType,SceneType int type = 0; std::string ogreName = ""; std::string ogreSecond = ""; std::string ogreText = ""; OgreStyle* parent = nullptr; OgreStyleVector childs; public: OgreStyle() { childs.clear(); } std::string getText() { if (ogreText.empty()) return ogreName; return ogreText; } void add(OgreStyle* child) { if (!check(child)) { throw exception("child is parent."); } if (child->parent != nullptr) { auto index = find(child->parent->childs.begin(), child->parent->childs.end(), child); child->parent->childs.erase(index); } child->parent = this; childs.push_back(child); } void remove(OgreStyle* child) { remove(child, true); } void removeAll() { //删除所有子节点 while (!childs.empty()) { auto child = childs.back(); childs.pop_back(); //这句 避免递归时重复delete child->parent = nullptr; if (child != nullptr) { delete child; child = nullptr; } } } OgreStyle* findChild(int type) { if (childs.empty()) return nullptr; for (auto child : this->childs) { if (child->type == type) return child; } return nullptr; } OgreStyle* findChild(std::string name) { if (childs.empty()) return nullptr; for (auto child : this->childs) { if (child->ogreName == name) return child; } return nullptr; } bool check(OgreStyle* style) { OgreStyle* parent = this->parent; while (parent != nullptr) { if (parent == style) return false; parent = parent->parent; } return true; } ~OgreStyle() { if (parent != nullptr) parent->remove(this, false); //删除所有子节点 while (!childs.empty()) { auto child = childs.back(); childs.pop_back(); //这句 避免递归时重复delete child->parent = nullptr; if (child != nullptr) { delete child; child = nullptr; } } } private: void remove(OgreStyle* child, bool bDelete) { if (child->parent != nullptr) { auto index = find(child->parent->childs.begin(), child->parent->childs.end(), child); child->parent->childs.erase(index); } child->parent = nullptr; if (bDelete) { delete child; child = nullptr; } } }; }
其中OgreStyle中的Type就是对应的Ogre中的对象类型,而ogreName是对应Ogre对象的标识,parent与childs树结构.type对应的值在如下类中:
namespace ResourceEnum { //对应Ogre资源类型 enum ResourceType { Resource = 100, Material, Technique, Pass, Mesh, SubMesh, TextureUnit, Texture, Particle, Compositor, Program, Shader_Cg, Shader_hlsl, Shader_glsl, }; //对应Ogre场景 enum SceneType { Scene = 200, SceneNode, Viewport, Camera, RenderSystem, RenderTarget, MovableObject, Renderable }; enum MovableType { MovableOther = 300, BillboardChain, BillboardSet, Entity, Light, ManualObject, ParticleSystem, RibbonTrail, }; enum RenderType { RenderOther = 400, RenderSubEntity }; enum FileType { FL_Materials = 500, FL_Material, FL_Group, }; }
先暂时列这么多,其中上面第一份XML文件就是每个type关联那些type,第二份文件就是每个type对应的字段,第三个type就是其中Ogre中的一些enum.下面这个类把type与三份三XML文件关联起来.其中方法initXML不是有意要使用这种比较难理解的方式,主要是因为DataStreamHolder里面的数据是shared_ptr智能指针类型,最后得到的root只有在函数initXML内才有效,传出这个值无效,而针对XML文件有效性检查的代码是一样,这样只有把方法传过来.
namespace Ogre3DX { struct NodeStyle { std::string nodeName; std::string nodeText; NodeStyle(){}; NodeStyle(std::string name, std::string text) :nodeName(name), nodeText(text){} string getText() { if (nodeText.empty()) { string result; for (auto ch = nodeName.begin(); ch != nodeName.end(); ++ch) { if (ch != nodeName.begin() && isupper(*ch)) { result.push_back(' '); } result.push_back(*ch); } return result; } return nodeText; } }; struct OgreField { string fieldName; string fieldType; string fieldKey; string fieldText; bool fieldManual = false; OgreField(){}; OgreField(std::string name, std::string type, std::string key) :fieldName(name), fieldType(type), fieldKey(key){} string getText() { if (fieldText.empty()) { string result; for (auto ch = fieldName.begin(); ch != fieldName.end(); ++ch) { if (ch != fieldName.begin() && isupper(*ch)) { result.push_back(' '); } result.push_back(*ch); } return result; } return fieldText; } }; struct RelationVisible { string name; string relationName; string relationValue; bool relationVisible; string nodeName; RelationVisible(std::string name, std::string dName, std::string value, bool visible, string node) :name(name), relationName(dName), relationValue(value), relationVisible(visible), nodeName(node){} }; typedef std::vector<OgreField*> FieldVector; typedef std::vector<NodeStyle*> NodeStyleVector; typedef std::vector<RelationVisible*> RelationVector; } namespace Ogre3DX { class OgreStyleManager : public tools::Singleton<OgreStyleManager> { public: OgreStyleManager(); ~OgreStyleManager(); void initialise(); int getOgreStyleType(const string& name, int deault = 0) { for (auto kv : ogreStyles) { if (kv.second == name) return kv.first; } return deault; } std::string getOgreStyleName(int type, const string& name = "") { if (ogreStyles.find(type) != ogreStyles.end()) { return ogreStyles[type]; } return name; } NodeStyleVector getNodeStyles(int type) { NodeStyleVector result; if (ogreStyles.find(type) != ogreStyles.end()) { auto styleName = ogreStyles[type]; if (ogreNodes.find(styleName) != ogreNodes.end()) { auto nodeNames = ogreNodes[styleName]; int size = nodeNames.size(); for (int i = 0; i < size; i++) { auto name = nodeNames[i]; auto nodeStyle = nodeStyles[name]; auto text = ogreTexts[styleName][i]; if (!text.empty()) nodeStyle->nodeText = text; result.push_back(nodeStyle); } } } return result; } FieldVector getFieldVector(const string& nodeName) { if (nodeFields.find(nodeName) != nodeFields.end()) { return nodeFields[nodeName]; } return FieldVector(); } VectorString getKeyItems(const string& key) { if (keyItems.find(key) != keyItems.end()) { return keyItems[key]; } return VectorString(); } RelationVector getRelationFields(const string& nodeName) { RelationVector result; for (auto rf : relationFields) { if (rf->nodeName == nodeName) result.push_back(rf); } return result; } bool getRelation(const string& fieldName) { for (auto rf : relationFields) { if (rf->name == fieldName || rf->relationName == fieldName) return true; } return false; } private: typedef void(OgreStyleManager::*ptrFun)(xml::ElementPtr root); void initXML(std::string xmlName, ptrFun func); void initOgreStyles(); void initStyleNodes(xml::ElementPtr root); void initNodeFields(xml::ElementPtr root); void initKeyItems(xml::ElementPtr root); void initRelationFields(); xml::ElementPtr getRoot(DataStreamHolder data); private: // OgreResource int -> OgreStyleName std::map<int, string> ogreStyles; //NodeName -> NodeStyle std::map<string, NodeStyle*> nodeStyles; //OgreStyleName -> Vector NodeName (StyleNode.xml) std::map<string, VectorString> ogreNodes; std::map<string, VectorString> ogreTexts; //NodeName -> Vector NodeField (NodeField.xml) std::map<string, FieldVector> nodeFields; //key - items (KeyItem.xml) std::map<string, VectorString> keyItems; RelationVector relationFields; }; } template <> OgreStyleManager* tools::Singleton<OgreStyleManager>::msInstance = nullptr; namespace Ogre3DX { OgreStyleManager::OgreStyleManager() { } OgreStyleManager::~OgreStyleManager() { } void OgreStyleManager::initialise() { initOgreStyles(); initXML("StyleNode.xml", &OgreStyleManager::initStyleNodes); initXML("NodeField.xml", &OgreStyleManager::initNodeFields); initXML("KeyItem.xml", &OgreStyleManager::initKeyItems); initRelationFields(); } void OgreStyleManager::initOgreStyles() { ogreStyles[ResourceEnum::ResourceType::Resource] = "Resource"; ogreStyles[ResourceEnum::ResourceType::Material] = "Material"; ogreStyles[ResourceEnum::ResourceType::Technique] = "Technique"; ogreStyles[ResourceEnum::ResourceType::Mesh] = "Mesh"; ogreStyles[ResourceEnum::ResourceType::Texture] = "Texture"; ogreStyles[ResourceEnum::ResourceType::Particle] = "Particle"; ogreStyles[ResourceEnum::ResourceType::Compositor] = "Compositor"; ogreStyles[ResourceEnum::ResourceType::Program] = "Program"; ogreStyles[ResourceEnum::ResourceType::SubMesh] = "SubMesh"; ogreStyles[ResourceEnum::ResourceType::Pass] = "Pass"; ogreStyles[ResourceEnum::SceneType::Scene] = "SceneManager"; ogreStyles[ResourceEnum::SceneType::SceneNode] = "SceneNode"; ogreStyles[ResourceEnum::SceneType::Viewport] = "Viewport"; ogreStyles[ResourceEnum::SceneType::Camera] = "Camera"; ogreStyles[ResourceEnum::SceneType::RenderSystem] = "RenderSystem"; ogreStyles[ResourceEnum::SceneType::RenderTarget] = "RenderTarget"; ogreStyles[ResourceEnum::SceneType::MovableObject] = "MovableObject"; ogreStyles[ResourceEnum::SceneType::Renderable] = "Renderable"; ogreStyles[ResourceEnum::MovableType::MovableOther] = "MovableOther"; ogreStyles[ResourceEnum::MovableType::BillboardChain] = "BillboardChain"; ogreStyles[ResourceEnum::MovableType::BillboardSet] = "BillboardSet"; ogreStyles[ResourceEnum::MovableType::Entity] = "Entity"; ogreStyles[ResourceEnum::MovableType::Light] = "Light"; ogreStyles[ResourceEnum::MovableType::ManualObject] = "ManualObject"; ogreStyles[ResourceEnum::MovableType::ParticleSystem] = "ParticleSystem"; ogreStyles[ResourceEnum::MovableType::RibbonTrail] = "RibbonTrail"; ogreStyles[ResourceEnum::RenderType::RenderSubEntity] = "SubEntity"; } void OgreStyleManager::initXML(std::string xmlName, ptrFun initFunc) { DataStreamHolder data = MyGUI::DataManager::getInstance().getData(xmlName); if (data.getData() == nullptr) { throw exception(""); } xml::Document doc; if (!doc.open(data.getData())) { throw exception(""); } xml::ElementPtr root = doc.getRoot(); if ((nullptr == root) || (root->getName() != "OgreResource")) { throw exception(""); } (this->*initFunc)(root); } void OgreStyleManager::initStyleNodes(xml::ElementPtr root) { auto node = root->getElementEnumerator(); while (node.next("OgreStyle")) { auto name = node->findAttribute("name"); auto field = node->getElementEnumerator(); while (field.next()) { auto nodeName = field->findAttribute("name"); auto text = field->findAttribute("text"); ogreNodes[name].push_back(nodeName); ogreTexts[name].push_back(text); } } } void OgreStyleManager::initNodeFields(xml::ElementPtr root) { auto node = root->getElementEnumerator(); while (node.next("OgreNode")) { auto name = node->findAttribute("name"); auto text = node->findAttribute("text"); NodeStyle* nodeStyle = new NodeStyle(name, text); nodeStyles[name] = nodeStyle; auto field = node->getElementEnumerator(); while (field.next()) { auto fieldName = field->findAttribute("name"); auto fieldType = field->findAttribute("type"); auto fieldKey = field->findAttribute("key"); auto fieldText = field->findAttribute("text"); auto fieldManual = field->findAttribute("manual"); OgreField* field = new OgreField(fieldName, fieldType, fieldKey); field->fieldText = fieldText; if (fieldManual.size() > 0) { field->fieldManual = Ogre::StringConverter::parseBool(fieldManual); } nodeFields[name].push_back(field); } } } void OgreStyleManager::initKeyItems(xml::ElementPtr root) { auto node = root->getElementEnumerator(); while (node.next("Key")) { auto name = node->findAttribute("name"); auto field = node->getElementEnumerator(); while (field.next()) { auto nodeName = field->findAttribute("name"); keyItems[name].push_back(nodeName); } } } void OgreStyleManager::initRelationFields() { RelationVisible* entitySkeleton = new RelationVisible("DisplaySkeleton", "HaveSkeleton", "false", false, "Entity"); RelationVisible* targetPrimary0 = new RelationVisible("Update", "Primary", "true", false, "RenderTarget"); RelationVisible* targetPrimary1 = new RelationVisible("Active", "Primary", "true", false, "RenderTarget"); RelationVisible* targetPrimary2 = new RelationVisible("AutoUpdated", "Primary", "true", false, "RenderTarget"); RelationVisible* spot = new RelationVisible("Spotlight", "Type", "SPOTLIGHT", true, "Light"); RelationVisible* spotInner = new RelationVisible("SpotlightInner", "Type", "SPOTLIGHT", true, "Light"); RelationVisible* spotOuter = new RelationVisible("SpotlightOuter", "Type", "SPOTLIGHT", true, "Light"); RelationVisible* spotFalloff = new RelationVisible("SpotlightFalloff", "Type", "SPOTLIGHT", true, "Light"); relationFields.push_back(entitySkeleton); relationFields.push_back(targetPrimary0); relationFields.push_back(targetPrimary1); relationFields.push_back(targetPrimary2); relationFields.push_back(spot); relationFields.push_back(spotInner); relationFields.push_back(spotOuter); relationFields.push_back(spotFalloff); } xml::ElementPtr OgreStyleManager::getRoot(DataStreamHolder data) { //Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(_name, mGroup, true); //MyGUI::OgreDataStream* data = new MyGUI::OgreDataStream(stream); // "OgreNodes.xml"); if (data.getData() == nullptr) { throw exception(""); } xml::Document doc; if (!doc.open(data.getData())) { throw exception(""); } xml::ElementPtr root = doc.getRoot(); if ((nullptr == root) || (root->getName() != "OgreResource")) { throw exception(""); } return root; } }
通过这个类,我们知道,一个OgreStyle对应多个NodeStyle,就如Entity分别显示Entiy, SceneNode, MovableObject 这三个节点,每个节点NodeStyle又包含多个OgreField,就如SceneManager包含name,typename,ambientLight等字段.上面的RelationVisible主要是指二个字段有主从关系的那种,如上面是聚光灯才显示一些聚光灯的信息,后面也会放入XML文件中.
然后就是OgreQuery,这个类比较复杂,在这我们只列出一个方法,就是得到我们现在树型控件上的数据.
OgreStyle* OgreQuery::getTLRoot() { ogreRoot = shared_ptr<OgreStyle>(new OgreStyle()); auto ogreSceneRoot = new OgreStyle(); auto scene = Ogre::Root::getSingleton().getSceneManager(DSceneName); ogreSceneRoot->type = ResourceEnum::SceneType::Scene; ogreSceneRoot->ogreName = scene->getName(); ogreRoot->add(ogreSceneRoot); auto factorys = Ogre::Root::getSingleton().getMovableObjectFactoryIterator(); while (factorys.hasMoreElements()) { auto currentFactory = factorys.getNext(); OgreStyle* factory = new OgreStyle(); factory->ogreName = currentFactory->getType(); factory->type = ResourceEnum::SceneType::MovableObject; ogreSceneRoot->add(factory); auto movables = scene->getMovableObjectIterator(factory->ogreName); while (movables.hasMoreElements()) { auto currentMovable = movables.getNext(); OgreStyle* movable = new OgreStyle(); movable->ogreName = currentMovable->getName(); int type = OgreStyleManager::getInstance().getOgreStyleType(currentMovable->getMovableType(), ResourceEnum::MovableType::MovableOther); movable->type = type; factory->add(movable); if (type == ResourceEnum::MovableType::Entity) { Ogre::Entity* entity = dynamic_cast<Ogre::Entity*>(currentMovable); if (MyGUI::utility::startWith(movable->ogreName, "Unnamed")) { movable->ogreText = entity->getMesh()->getName(); } int subCount = entity->getNumSubEntities(); for (int i = 0; i < subCount; i++) { auto subEntity = entity->getSubEntity(i); OgreStyle* subStyle = new OgreStyle(); movable->add(subStyle); getRenderable(subEntity, subStyle, ResourceEnum::RenderType::RenderSubEntity, i); } } } } auto ogreRenderRoot = new OgreStyle(); ogreRoot->add(ogreRenderRoot); auto render = Ogre::Root::getSingleton().getRenderSystem(); getRenderSystem(render, ogreRenderRoot); return ogreRoot.get(); }
这个类现在设计了二种显示方式,一种是上面的这种,不显示SceneNode,还有一种是显示SceneNode,这里就不放出来了,和上面这个方法差不多.
自动生成界面
第二部分全是UI显示部分,主要是控件显示分类,控件创建,显示,更新.
我们把我们要的控件分成如下几种显示方式,大部分是从MyGUI中的LayoutEditor中修改后直接使用.
1.PropertyFieldLabel:主要是显示一个文本,主要用于表示分组.
2.PropertyFieldEditBox:显示一个文本与一个值,一般用来设定字符串,如Technique象的名称.
3.PropertyFieldColour:颜色选择.对应对象的颜色.
4.PropertyFieldCheckBox:对应Bool类型,是或否.
5.PropertyFieldComboBox:对应多项选择,如Ogre常见的Enum,对光源类型,对应NodeField.xml文件中的type="ComboBox",这种type一般后面会有一个key的属性,用于去KeyItem.xml查找对应的多项显示,如光源类型对应的点光源,方向光源,聚光灯这些.
6.PropertyFieldAutoComplete:自定义MyGUI控件AutoComplete的包装,用于项非常多的时候,如天龙中,一个场景中Mesh与Material,一般成百上千的,下拉框根本显示不过来,AutoComplete能根据输入给出最适合的选择.后面会专门写一篇针对MyGUI各控件的扩展.
7.PropertyFieldNumeric:对应各type有n int,n float,用于检查输入是否合法.
其中PropertyFieldManager用于管理这些类,我们先看这个类的实现.
namespace tools { class PropertyFieldManager : public MyGUI::Singleton<PropertyFieldManager> { public: PropertyFieldManager(); virtual ~PropertyFieldManager(); void initialise(); void shutdown(); IPropertyField* createPropertyField(MyGUI::Widget* _window, const std::string& _type); private: typedef MyGUI::delegates::CDelegate2<IPropertyField*&, MyGUI::Widget*> Delegate; typedef std::map<std::string, Delegate> MapFactoryItem; MapFactoryItem mFactories; }; } template <> tools::PropertyFieldManager* MyGUI::Singleton<tools::PropertyFieldManager>::msInstance = nullptr; template <> const char* MyGUI::Singleton<tools::PropertyFieldManager>::mClassTypeName = "PropertyFieldManager"; namespace tools { template <typename Type> class GenericFactory { public: typedef MyGUI::delegates::CDelegate2<IPropertyField*&, MyGUI::Widget*> Delegate; static typename Delegate::IDelegate* getFactory() { return MyGUI::newDelegate(createFromFactory); } private: static void createFromFactory(IPropertyField*& _instance, MyGUI::Widget* _parent) { _instance = new Type(_parent); } }; PropertyFieldManager::PropertyFieldManager() { } PropertyFieldManager::~PropertyFieldManager() { } void PropertyFieldManager::initialise() { mFactories["EditBox"] = GenericFactory<PropertyFieldEditBox>::getFactory(); mFactories["Label"] = GenericFactory<PropertyFieldLabel>::getFactory(); mFactories["Colour"] = GenericFactory<PropertyFieldColour>::getFactory(); mFactories["Bool"] = GenericFactory<PropertyFieldCheckBox>::getFactory(); mFactories["ComboBox"] = GenericFactory<PropertyFieldComboBox>::getFactory(); mFactories["Auto"] = GenericFactory<PropertyFieldAutoComplete>::getFactory(); mFactories["StaticEditBox"] = GenericFactory<PropertyFieldStaticEditBox>::getFactory(); mFactories["1 int"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["2 int"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["4 int"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["1 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["2 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["3 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); mFactories["4 float"] = GenericFactory<PropertyFieldNumeric>::getFactory(); } void PropertyFieldManager::shutdown() { } IPropertyField* PropertyFieldManager::createPropertyField(MyGUI::Widget* _window, const std::string& _type) { IPropertyField* result = nullptr; MapFactoryItem::iterator item = mFactories.find(_type); MYGUI_ASSERT(item != mFactories.end(), "Factory PropertyField '" << _type << "' not found."); (*item).second(result, _window); result->initialise(_type); return result; } }
PropertyFieldManager主要是根据不同的NodeField.xml中NodeField中的Type来生成对应IPropertyField.IPropertyField是上面各字段控件的基类,我们来看下他的实现,了解所有字段控件实现能那些功能.对比Mygui中的LayoutEditor中的IPropertyField有些改动主要一是为了让IPropertyField承载更多公共实现,免的子类型的各部分代码差不多.二是更好的适合我们自己的项目,针对Ogre的各属性更改.
using namespace Ogre3DX; namespace tools { typedef MyGUI::delegates::CDelegate3<const std::string&, const std::string&, bool> PropertyFieldActionDelegate; class IPropertyField { public: virtual ~IPropertyField(); virtual void initialise(const std::string& _type); virtual void setValue(const std::string& _value) = 0; //Relation Field virtual string getValue(); virtual void onFillValues(); virtual void setField(OgreField* _field); virtual OgreField* getField(); //Update Field virtual bool getFocus(); virtual void setName(const std::string& _value); virtual void setVisible(bool _value); virtual bool getVisible(); virtual void onAction(const std::string& _value, bool _final); virtual bool onCheckValue(); virtual MyGUI::IntSize getContentSize(); virtual void setCoord(const MyGUI::IntCoord& _coord); virtual bool checkFocus(MyGUI::Widget* widget); PropertyFieldActionDelegate eventAction; protected: MyGUI::TextBox* mText = nullptr; std::string mType; std::string mName; OgreField* field = nullptr; MyGUI::Widget* mainWidget = nullptr; }; } namespace tools { IPropertyField::~IPropertyField() { } void IPropertyField::initialise(const std::string& _type) { mType = _type; } string IPropertyField::getValue() { return ""; } void IPropertyField::onFillValues() { } void IPropertyField::setField(OgreField* _field) { field = _field; onFillValues(); } OgreField* IPropertyField::getField() { return field; } bool IPropertyField::getFocus() { //InputManager::getInstance().getKeyFocusWidget(); return false; } bool IPropertyField::checkFocus(MyGUI::Widget* widget) { auto mouseF = MyGUI::InputManager::getInstance().getMouseFocusWidget(); if (mouseF == widget) return true; auto keyF = MyGUI::InputManager::getInstance().getKeyFocusWidget(); if (keyF == widget) return true; return false; } void IPropertyField::onAction(const std::string& _value, bool _final) { eventAction(field->fieldName, _value, _final); } bool IPropertyField::onCheckValue() { return true; } MyGUI::IntSize IPropertyField::getContentSize() { return MyGUI::IntSize(0, mainWidget->getHeight()); } void IPropertyField::setCoord(const MyGUI::IntCoord& _coord) { mainWidget->setCoord(_coord); } void IPropertyField::setName(const std::string& _value) { mName = _value; mText->setCaption(_value); } void IPropertyField::setVisible(bool _value) { mainWidget->setVisible(_value); } bool IPropertyField::getVisible() { return mainWidget->getVisible(); } }
可以看到IPropertyField与第一部分中的OgreField是一一对应的,拿一些方法说下.
1.initialise():子类主要根据对应的Layout文件生成各个控件.在这主要把BaseLayout中的mMainWidget给IPropertyField中的mainWidget,这样子类就不需要每个来实现getVisible,setVisible,getContentSize,setCoord就不需要子类型去实现了.
2.setValue,getValue:设置和得到IPropertyField控件中的值.
3.setField,getField:设置和得到对应的OgreField.
4.getFocus:检查当前IPropertyField是否在得到鼠标或键盘的焦点.如果是,我们不去更新上面的值.
5.onAction:当我们更新IPropertyField中的值后,我们需要通知事件上的各个方法.
如下是PropertyFieldComboBox的具体实现,这个类可以让我们更好理解相关方法需要如何实现.
namespace tools { class PropertyFieldComboBox : public wraps::BaseLayout, public IPropertyField { public: PropertyFieldComboBox(MyGUI::Widget* _parent); virtual ~PropertyFieldComboBox(); virtual void setValue(const std::string& _value); virtual string getValue(); protected: virtual void onFillValues(); private: void notifyApplyProperties(MyGUI::Widget* _sender); void notifyForceApplyProperties2(MyGUI::ComboBox* _widget, size_t _index); protected: MyGUI::ComboBox* mField; }; } namespace tools { PropertyFieldComboBox::PropertyFieldComboBox(MyGUI::Widget* _parent) : BaseLayout("PropertyFieldComboBox.layout", _parent), mField(nullptr) { assignWidget(mText, "Text"); assignWidget(mField, "Field"); mainWidget = mMainWidget; mField->eventComboAccept += newDelegate(this, &PropertyFieldComboBox::notifyForceApplyProperties2); } PropertyFieldComboBox::~PropertyFieldComboBox() { } void PropertyFieldComboBox::onFillValues() { if (field == nullptr || field->fieldKey.empty()) return; mField->removeAllItems(); auto items = OgreStyleManager::getInstance().getKeyItems(field->fieldKey); for (auto item : items) { mField->addItem(item); } } void PropertyFieldComboBox::notifyApplyProperties(MyGUI::Widget* _sender) { std::string value = MyGUI::utility::toString(mField->getIndexSelected());// mField->getOnlyText(); onAction(value, true); } void PropertyFieldComboBox::notifyForceApplyProperties2(MyGUI::ComboBox* _sender, size_t _index) { notifyApplyProperties(_sender); } void PropertyFieldComboBox::setValue(const std::string& _value) { int index = MyGUI::utility::parseInt(_value); if (index >= 0 && index < mField->getItemCount()) mField->setItemSelect(index); //mField->setOnlyText(_value); } string PropertyFieldComboBox::getValue() { return mField->getOnlyText(); } }
当我们知道如何根据OgreField生成IPropertyField后,如前面一个OgreStyle对应多个NodeStyle,每个节点NodeStyle又包含多个OgreField.对应PropertiesPanelView包含多个PanelProperties.每个PanelProperties又包含多个IPropertyField.
当PropertiesPanelView得到OgreStyle后,根据OgreStyle的NodeStyle生成PanelProperties,然后PanelProperties根据对应的NodeStyle中的OgreField 生成IPropertyField.这样OgreStyle需要的各个控件显示出来了,如下是相关PropertiesPanelView与PanelProperties代码.
namespace tools { class PropertiesPanelView : public wraps::BaseLayout { public: PropertiesPanelView(MyGUI::Widget* _parent = nullptr); virtual ~PropertiesPanelView(); void notifyChangeSelectedWidget(OgreStyle* data); void hideAllPanel(); private: void notifyWindowChangeCoord(MyGUI::Window* _sender); std::vector<PanelProperties*> getPropertyWindow(OgreStyle* _style); size_t getIndexPanel(PanelProperties* _panel); void onRefleshFiled(float time); private: MyGUI::IntSize mOldSize; PanelView* mPanelView; typedef std::map<string, PanelProperties*> MapPropertyWindow; MapPropertyWindow mMapPropertyWindow; OgreStyle* currentStyle; }; } namespace tools { PropertiesPanelView::PropertiesPanelView(MyGUI::Widget* _parent) : BaseLayout("PropertiesPanelView.layout", _parent), mPanelView(nullptr), currentStyle(nullptr) { assignBase(mPanelView, "scroll_View"); MyGUI::Window* window = mMainWidget->castType<MyGUI::Window>(false); if (window != nullptr) { window->eventWindowChangeCoord += MyGUI::newDelegate(this, &PropertiesPanelView::notifyWindowChangeCoord); mOldSize = window->getSize(); } notifyChangeSelectedWidget(nullptr); Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &PropertiesPanelView::onRefleshFiled); } PropertiesPanelView::~PropertiesPanelView() { mPanelView->removeAllItems(); for (MapPropertyWindow::iterator item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) delete (*item).second; mMapPropertyWindow.clear(); } void PropertiesPanelView::notifyWindowChangeCoord(MyGUI::Window* _sender) { const MyGUI::IntSize& size = _sender->getSize(); if (size != mOldSize) { mOldSize = size; mPanelView->setNeedUpdate(); } } void PropertiesPanelView::notifyChangeSelectedWidget(OgreStyle* _currentStyle) { currentStyle = _currentStyle; for (MapPropertyWindow::iterator item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) { (*item).second->setVisible(false); (*item).second->setStyle(nullptr, nullptr); (*item).second->update(); } if (currentStyle == nullptr) return; auto panels = getPropertyWindow(currentStyle); for (auto panel : panels) { panel->setVisible(true); panel->update(); } } void PropertiesPanelView::hideAllPanel() { for (MapPropertyWindow::iterator item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) { (*item).second->setVisible(false); (*item).second->setStyle(nullptr, nullptr); (*item).second->update(); } } std::vector<PanelProperties*> PropertiesPanelView::getPropertyWindow(OgreStyle* _style) { std::vector<PanelProperties*> result; auto nodeStyles = OgreStyleManager::getInstance().getNodeStyles(_style->type); for (auto nodeStyle : nodeStyles) { if (nodeStyle == nullptr || nodeStyle->nodeName.empty()) continue; std::string key = MyGUI::utility::toString(_style->type) + nodeStyle->nodeName; MapPropertyWindow::iterator item = mMapPropertyWindow.find(key); if (item == mMapPropertyWindow.end()) { PanelProperties* panel = new PanelProperties(); mPanelView->addItem(panel);// insertItem(getIndexByDepth(_depth), result); mMapPropertyWindow[key] = panel; } mMapPropertyWindow[key]->setStyle(_style, nodeStyle); result.push_back(mMapPropertyWindow[key]); } return result; } size_t PropertiesPanelView::getIndexPanel(PanelProperties* _panel) { for (size_t index = 0; index < mPanelView->getItemCount(); ++index) { if (mPanelView->getItem(index) == _panel) return index; } return MyGUI::ITEM_NONE; } void PropertiesPanelView::onRefleshFiled(float time) { for (auto item = mMapPropertyWindow.begin(); item != mMapPropertyWindow.end(); ++item) { if ((*item).second->getVisible()) { (*item).second->onReflesh(); } } } }
using namespace Ogre3DX; namespace tools { typedef std::vector<std::pair<std::string, IPropertyField*>> MapPropertyField; typedef MapPropertyField::iterator FieldIter; class PanelProperties : public wraps::BasePanelViewItem { public: PanelProperties(); virtual void initialise(); virtual void shutdown(); void setStyle(OgreStyle* ogreStyle, NodeStyle* nodeStyle); void update(); void onReflesh(); private: void notifyAction(const std::string& _name, const std::string& _value, bool _final); size_t addParametrs(); void destroyPropertyFields(); void hidePropertyFields(); void updateSize(); void updateRelationFields(); IPropertyField* getPropertyField(MyGUI::Widget* _client, OgreField* field); FieldIter getField(const std::string& name); private: //typedef std::map<std::string, IPropertyField*> MapPropertyField; MapPropertyField mFields; OgreStyle* currentOgreStyle = nullptr; NodeStyle* currentNodeStyle = nullptr; }; } namespace tools { PanelProperties::PanelProperties() : BasePanelViewItem("PanelProperties.layout") { } void PanelProperties::initialise() { } void PanelProperties::shutdown() { destroyPropertyFields(); } size_t PanelProperties::addParametrs() { size_t result = 0; if (currentNodeStyle != nullptr) { auto fields = OgreStyleManager::getInstance().getFieldVector(currentNodeStyle->nodeName); for (auto iter = fields.begin(); iter != fields.end(); ++iter) { IPropertyField* field = getPropertyField(mWidgetClient, (*iter)); field->setField(*iter); field->setValue(""); result++; } } return result; } void PanelProperties::setStyle(OgreStyle* ogreStyle, NodeStyle* nodeStyle) { currentOgreStyle = ogreStyle; currentNodeStyle = nodeStyle; } void PanelProperties::update() { hidePropertyFields(); if (currentNodeStyle != nullptr) mPanelCell->setCaption(currentNodeStyle->getText()); size_t count = addParametrs(); setVisible(count > 0); onReflesh(); updateSize(); updateRelationFields(); } void PanelProperties::onReflesh() { for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) { auto fieldLayout = (*item).second; if (fieldLayout->getVisible() && !fieldLayout->getFocus()) { auto field = fieldLayout->getField(); if (field->fieldManual) continue; if (currentOgreStyle != nullptr && currentNodeStyle != nullptr && field != nullptr) { int type = OgreStyleManager::getInstance().getOgreStyleType(currentNodeStyle->nodeName); if (type != 0) { auto text = OgreManager::getInstance().getValue(currentOgreStyle, type, field->fieldName); fieldLayout->setValue(text); } } } } } void PanelProperties::updateSize() { int height = 0; for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) { if ((*item).second->getVisible()) { MyGUI::IntSize size = (*item).second->getContentSize(); (*item).second->setCoord(MyGUI::IntCoord(0, height, mMainWidget->getWidth(), size.height)); height += size.height; } } mPanelCell->setClientHeight(height); } void PanelProperties::updateRelationFields() { if (currentNodeStyle == nullptr) return; RelationVector rfs = OgreStyleManager::getInstance().getRelationFields(currentNodeStyle->nodeName); if (rfs.size() == 0) return; bool change = false; for (auto rf : rfs) { auto source = getField(rf->name); auto dest = getField(rf->relationName); if (source != mFields.end() && dest != mFields.end()) { auto nowValue = (*dest).second->getValue(); bool nowVisable = (*source).second->getVisible(); if ((nowValue == rf->relationValue) != (nowVisable == rf->relationVisible)) { change = true; (*source).second->setVisible(!nowVisable); } } } if (change) { updateSize(); } } void PanelProperties::destroyPropertyFields() { for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) delete (*item).second; mFields.clear(); } void PanelProperties::notifyAction(const std::string& _name, const std::string& _value, bool _final) { //if (currentOgreStyle != nullptr) //{ // currentOgreStyle->setValue(_name, _value); //} if (currentOgreStyle != nullptr && currentNodeStyle != nullptr) { int type = OgreStyleManager::getInstance().getOgreStyleType(currentNodeStyle->nodeName); if (type != 0) { OgreManager::getInstance().setValue(currentOgreStyle, type, _name, _value); } } updateRelationFields(); } void PanelProperties::hidePropertyFields() { for (MapPropertyField::iterator item = mFields.begin(); item != mFields.end(); ++item) (*item).second->setVisible(false); } FieldIter PanelProperties::getField(const std::string& name) { for (auto i = mFields.begin(); i != mFields.end(); ++i) { if ((*i).first == name) return i; } return mFields.end(); } IPropertyField* PanelProperties::getPropertyField(MyGUI::Widget* _client, OgreField* field) { MapPropertyField::iterator item = getField(field->fieldName); if (item != mFields.end()) { (*item).second->setVisible(true); return (*item).second; } IPropertyField* result = PropertyFieldManager::getInstance().createPropertyField(_client, field->fieldType); result->setName(field->getText()); result->eventAction = MyGUI::newDelegate(this, &PanelProperties::notifyAction); //mFields[_name] = result; mFields.push_back(std::make_pair(field->fieldName, result)); return result; } }
这样第二部分就把XML与控件生成关联起来了,下面是第三部分,主要管理控件更新.
Oger对象驱动更新界面
最开始我这边的处理是OgreStyle里关联一个Ogre对象,如OgreStyle中的type是Camera,就关联一个Ogre中的Camera对象,这样相应更新就直接根据这个对象来处理,不过后面没有这样处理,主要是如下考虑,一是OgreStyle被太多类引用,最好不要过于复杂,首先更新必然会修改到更多类,其次在C++中更新这种很多类引用的文件编译都要等死个人.二是我们的需求,如前面所说Entity类型的OgreStyle需要更新Entity的属性,也可能是MovableObject或SceneNode,这样绑定OgreStyle与Ogre对象后,并不好处理这种情况,因为并不是一对一,根据修改的Field的名字来查找对应的NodeStyle并不是一个好的选择.基于上面考虑,放弃这种想法.
我们仔细想一下,应该是每一种type一种更新方式,并不和OgreStyle有关,而是和OgreStyle中的type有关,如OgreStyle为Entity,但是type可能是Entiyt, MovableObject, SceneNode这三种,我们可以根据type查找如何更新Ogre对象各字段的方法,然后根据OgreStyle的Ogrename找到对象.如下是代码:
namespace Ogre3DX { class OgreManager : public tools::Singleton<OgreManager> { public: OgreManager() { mapObject[ResourceEnum::ResourceType::Resource] = new OgreResource(); mapObject[ResourceEnum::ResourceType::Material] = new OgreMaterial(); mapObject[ResourceEnum::ResourceType::Technique] = new OgreTechnique(); mapObject[ResourceEnum::ResourceType::Pass] = new OgrePass(); mapObject[ResourceEnum::SceneType::Viewport] = new OgreViewport(); mapObject[ResourceEnum::SceneType::SceneNode] = new OgreSceneNode(); mapObject[ResourceEnum::SceneType::MovableObject] = new OgreMovableObject(); mapObject[ResourceEnum::SceneType::RenderTarget] = new OgreRenderTarget(); mapObject[ResourceEnum::SceneType::Camera] = new OgreCamera(); mapObject[ResourceEnum::SceneType::Renderable] = new OgreRenderable(); mapObject[ResourceEnum::SceneType::Scene] = new OgreSceneManager(); mapObject[ResourceEnum::MovableType::Entity] = new OgreEntity(); mapObject[ResourceEnum::MovableType::Light] = new OgreLight(); scene = Ogre::Root::getSingleton().getSceneManager(DSceneName); } std::string OgreManager::getValue(OgreStyle* style, int type, const string& field) { OgreBasic* basic = getObject(style, type); if (basic != nullptr && basic->getObject() != nullptr) { return basic->getValue(field); } return ""; } bool OgreManager::setValue(OgreStyle* style, int type, const string& field, const string& value) { OgreBasic* basic = getObject(style, type); if (basic != nullptr && basic->getObject() != nullptr) { return basic->setValue(field, value); } return false; } OgreBasic* getObject(OgreStyle* style, int type) { auto object = getType(type); if (object == nullptr) return nullptr; //重新设置ogre值. if (style != preStyle || type != preType) { fillStyleObject(style, type); preStyle = style; preType = type; } return object; } OgreBasic* getType(int type) { if (mapObject.find(type) != mapObject.end()) { return mapObject[type]; } return nullptr; } void fillStyleObject(OgreStyle* style, int type) { auto object = getType(type); if (object == nullptr) return; switch (style->type) { case ResourceEnum::SceneType::Scene: if (true) { if (type == ResourceEnum::SceneType::Scene) { object->setObject(scene); } else if (type == ResourceEnum::SceneType::Viewport) { object->setObject(scene->getCurrentViewport()); } } break; case ResourceEnum::MovableType::Entity: if (true) { auto entity = scene->getEntity(style->ogreName); if (type == ResourceEnum::MovableType::Entity) { object->setObject(entity); } fillMovableable(object, entity, type); } break; case ResourceEnum::ResourceType::Material: if (true) { auto mat = MaterialManager::getSingleton().getByName(style->ogreName); fillMaterial(object, mat, type); } break; case ResourceEnum::RenderType::RenderSubEntity: if (style->parent != nullptr && style->parent->type == ResourceEnum::MovableType::Entity) { auto entity = scene->getEntity(style->parent->ogreName); int index = Ogre::StringConverter::parseInt(style->ogreName); auto subEntity = entity->getSubEntity(index); fillRenderable(object, subEntity, type); } break; case ResourceEnum::SceneType::RenderTarget: if (true) { auto render = Ogre::Root::getSingleton().getRenderSystem(); auto target = render->getRenderTarget(style->ogreName); if (type == ResourceEnum::SceneType::RenderTarget) { object->setObject(target); } } break; case ResourceEnum::SceneType::Viewport: if (true) { RenderSystem* render = Ogre::Root::getSingleton().getRenderSystem(); if (style->parent != nullptr && style->parent->type == ResourceEnum::SceneType::RenderTarget) { auto target = render->getRenderTarget(style->parent->ogreName); int index = Ogre::StringConverter::parseInt(style->ogreName); auto viewport = target->getViewport(index); auto camera = viewport->getCamera(); if (type == ResourceEnum::SceneType::Viewport) { object->setObject(viewport); } else if (camera != nullptr) { fillCamera(object, camera, type); } } } break; case ResourceEnum::SceneType::Camera: if (true) { auto camera = scene->getCamera(style->ogreName); if (camera != nullptr) { fillCamera(object, camera, type); } } break; case ResourceEnum::MovableType::Light: if (true) { auto light = scene->getLight(style->ogreName); if (type == ResourceEnum::MovableType::Light) { object->setObject(light); } fillMovableable(object, light, type); } break; default: break; } } void fillCamera(OgreBasic* object, Camera* camera, int type) { if (type == ResourceEnum::SceneType::Camera) { object->setObject(camera); } else if (type == ResourceEnum::SceneType::MovableObject) { object->setObject(camera); } else if (type == ResourceEnum::SceneType::Renderable) { object->setObject(camera); } } void fillRenderable(OgreBasic* object, Renderable* render, int type) { if (type == ResourceEnum::SceneType::Renderable) { object->setObject(render); } else if (type == ResourceEnum::ResourceType::Material) { object->setObject(render->getMaterial().get()); } else if (type == ResourceEnum::ResourceType::Technique) { auto tech = render->getTechnique(); object->setObject(tech); } else if (type == ResourceEnum::ResourceType::Pass) { auto pass = render->getTechnique()->getPass(0); object->setObject(pass); } } void fillMaterial(OgreBasic* object, MaterialPtr material, int type) { if (type == ResourceEnum::ResourceType::Material) { object->setObject(material.get()); } else if (type == ResourceEnum::ResourceType::Resource) { object->setObject(material.get()); } else if (type == ResourceEnum::ResourceType::Technique) { auto tech = material->getBestTechnique(); if (tech == nullptr) { tech = material->getTechnique(0); } object->setObject(tech); } else if (type == ResourceEnum::ResourceType::Pass) { auto tech = material->getBestTechnique(); if (tech == nullptr) { tech = material->getTechnique(0); } object->setObject(tech->getPass(0)); } } void fillMovableable(OgreBasic* object, MovableObject* movable, int type) { if (type == ResourceEnum::SceneType::MovableObject) { object->setObject(movable); } else if (type == ResourceEnum::SceneType::SceneNode) { object->setObject(movable->getParentSceneNode()); } } ~OgreManager() { } private: std::map<int, OgreBasic*> mapObject; //OgreBasic* pre OgreStyle* preStyle = nullptr; int preType = 0; SceneManager* scene = nullptr; }; }
其中OgreBasic是所有ogre对象更新类的基类,OgreTemplate是一个泛型类,提供一些方便.如下代码:
using namespace Ogre; typedef Ogre::StringConverter format; namespace Ogre3DX { class OgreBasic { public: virtual std::string getValue(std::string field) = 0; virtual bool setValue(std::string field, std::string value) = 0; virtual void setObject(void* object){}; virtual void* getObject(){ return nullptr; } }; template<typename T> class OgreTemplate : public OgreBasic { public: OgreTemplate() { } virtual ~OgreTemplate(){}; virtual std::string getValue(std::string field) { return ""; } virtual bool setValue(std::string field, std::string value) { return false; } virtual void setObject(T* object) { ogreObject = object; } virtual T* getOgreObject() { return ogreObject; } protected: virtual void setObject(void* object) { return setObject((T*)object); } virtual void* getObject() { if (ogreObject == nullptr) return nullptr; return (void*)ogreObject; } protected: T* ogreObject = nullptr; }; }
其中OgreBasic是根据字段名得到对应Ogre属性的值,setValue是对某个字段赋值,setObject是传入相关Ogre对象指针,getObject是返回某对象指针,而OgreTemplate主要给我们提供一些方便,如上面都是void*指针,在C#中相当于object一样,需要强制转化后才能用,这个OgreTemplate自动帮我们做这些事件,基于最少可见原则,在OgreTemplate我们把父类中的setObject与getObject隐藏,我们并不希望用OgreTemplate泛型类后还在使用对应void*指针,这样可以避免很多不安全的问题.而OgreBasic本身是给OgreManager使用,在这个类中,会保证生成正确的OgreTemplate,传入正确的Ogre对象指针.
在这我们给出OgreSceneManager的一个OgreTemplate泛型具体化实现.
namespace Ogre3DX { class OgreSceneManager : public OgreTemplate<Ogre::SceneManager> { private: Plane* plane = nullptr; std::string planeMaterial = ""; FogMode fogMode = FogMode::FOG_NONE; ColourValue fogColor = ColourValue::White; Real fogDensity = 0.001; Real fogStart = 0.0; Real fogEnd = 1.0; public: OgreSceneManager() { plane = new Plane(0, -1, 0, 5000); } ~OgreSceneManager() { if (plane != nullptr) { delete plane; plane = nullptr; } } virtual std::string getValue(std::string field) { std::string result = ""; if (field == "Name") { result = ogreObject->getName(); } else if (field == "TypeName") { result = ogreObject->getTypeName(); } else if (field == "AmbientLight") { result = format::toString(ogreObject->getAmbientLight()); } else if (field == "PlaneEnable") { result = format::toString(ogreObject->isSkyPlaneEnabled()); } else if (field == "Plane") { Vector4 vp(plane->normal.x, plane->normal.y, plane->normal.z, plane->d); result = format::toString(vp); } else if (field == "BoxEnable") { result = format::toString(ogreObject->isSkyBoxEnabled()); } else if (field == "DomeEnable") { result = format::toString(ogreObject->isSkyDomeEnabled()); } else if (field == "FogMode") { result = format::toString(ogreObject->getFogMode()); } else if (field == "FogColour") { result = format::toString(ogreObject->getFogColour()); } else if (field == "FogStart") { result = format::toString(ogreObject->getFogStart()); } else if (field == "FogEnd") { result = format::toString(ogreObject->getFogEnd()); } else if (field == "FogDensity") { result = format::toString(ogreObject->getFogDensity()); } else if (field == "ShadowTechnique") { int index = 0; auto st = ogreObject->getShadowTechnique(); if (st == 0x12) { index = 1; } else if (st == 0x11) { index = 2; } else if (st == 0x22) { index = 3; } else if (st == 0x21) { index = 4; } else if (st == 0x25) { index = 5; } else if (st == 0x26) { index = 6; } result = format::toString(index); } else if (field == "ShadowColour") { result = format::toString(ogreObject->getShadowColour()); } else if (field == "ShadowFarDistance") { result = format::toString(ogreObject->getShadowFarDistance()); } return result; } virtual bool setValue(std::string field, std::string value) { if (field == "AmbientLight") { auto ambient = format::parseColourValue(value); ogreObject->setAmbientLight(ambient); } else if (field == "PlaneEnable") { auto check = format::parseBool(value); ogreObject->setSkyPlaneEnabled(check); } else if (field == "Plane" || field == "PlaneMaterial") { if (field == "Plane") { auto v4 = format::parseVector4(value); plane->normal.x = v4.x; plane->normal.y = v4.y; plane->normal.z = v4.z; plane->d = v4.w; } else if (field == "PlaneMaterial") { planeMaterial = value; } if (!planeMaterial.empty()) { ogreObject->setSkyPlane(true, *plane, planeMaterial); } } else if (field == "BoxEnable") { auto check = format::parseBool(value); ogreObject->setSkyBoxEnabled(check); } else if (field == "DomeEnable") { auto check = format::parseBool(value); ogreObject->setSkyDomeEnabled(check); } else if (field == "BoxMaterial") { if (!value.empty()) { ogreObject->setSkyBox(true, value); } } else if (field == "DomeMaterial") { if (!value.empty()) { ogreObject->setSkyDome(true, value); } } if (field == "FogMode" || field == "FogColour" || field == "FogStart" || field == "FogEnd" || field == "FogDensity") { if (field == "FogMode") { fogMode = (FogMode)format::parseInt(value); } else if (field == "FogColour") { fogColor = format::parseColourValue(value); } else if (field == "FogStart") { fogStart = format::parseReal(value); } else if (field == "FogEnd") { fogEnd = format::parseReal(value); } else if (field == "FogDensity") { fogDensity = format::parseReal(value); } ogreObject->setFog(fogMode, fogColor, fogDensity, fogStart, fogEnd); } else if (field == "ShadowTechnique") { int index = format::parseInt(value); ShadowTechnique st = (ShadowTechnique)index; if (index == 1) { st = ShadowTechnique::SHADOWTYPE_STENCIL_MODULATIVE; } else if (index == 2) { st = ShadowTechnique::SHADOWTYPE_STENCIL_ADDITIVE; } else if (index == 3) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_MODULATIVE; } else if (index == 4) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_ADDITIVE; } else if (index == 5) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED; } else if (index == 6) { st = ShadowTechnique::SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED; } ogreObject->setShadowTechnique(st); } else if (field == "ShadowColour") { auto color = format::parseColourValue(value); ogreObject->setShadowColour(color); } else if (field == "ShadowFarDistance") { auto dist = format::parseReal(value); ogreObject->setShadowFarDistance(dist); } return true; } }; }
需要注意的是ShadowTechnique里我们KeyItem.xml文件里列出的项并没与Ogre本身的对应,所以需要在这处理一下,如果是正常一一对应的,直接tostring与parseInt后就行. 然后别的Ogre对象分别实现的OgreTemplate泛型具体化类都差不多是这种处理.
回头我们再看PanelProperties中updateRelationFields与notifyAction的处理,前者是每桢根据当前NodeStyle的对应type找到对应的OgreTemplate泛型具体化子类.然后根据当前OgreStyle的OgreName找到对应Ogre对应,然后根据字段,或是更新,更新得到对应OgreTemplate泛型具体化子类中的值.
至于为什么要每桢更新,而不是根据UI操作更新,简单来说,是为了避免BUG产生,如上面我们点击SceneNode的visible为flase后,对应节点下的MovableObject的visible也变成false,如果你不设置对应UI更新,对应的MovableObject上的visible还是显示为true,如果你去更新UI,可以说这样工作量又变大了,有一些你也想不到的关联,这样就等着更新大量的BUG去吧.其实这二种更新方式,对应我们官方的话来说:一个是事件驱动,一个是数据驱动.所以我们也不要以为WPF中的数据驱动是什么高大上的概念,就是因为如这里样WPF关联了控件与对应数据,数据更新后,每桢渲染时,根据数据显示对应UI状态.
如上通过这三个部分,我们实现了控件根据XML自动生成,控件根据Ogre对象自动更新.
当这个方案完成后,我只花了二个晚上的时间就完成了如上图中的Ogre对象的各个UI界面更新,完成不需要更新任何有关UI控件的代码,只要三个步骤就行,一是更新XML文件,二是具体化对应泛型OgreTemplate的实现,三是在OgreManager关联Ogre对象与泛型OgreTemplate就行,在可见的将来,对应的粒子,地形,合成器的编辑都可以用这种方式.
PS 2016.1.12 因为现在这个设计不满足Ogre2.1,暂时没有继续完成下去的欲望,代码放git上免的让别人认为是搞笑,就直接放http://pan.baidu.com/s/1kTWdqnl 这里,vs2013,相应DLL已经组织好,给mygui与ogre新手参考.