From 4219f16f61276e07067e005b850f058a41237f46 Mon Sep 17 00:00:00 2001 From: timoore Date: Wed, 15 Jul 2009 23:10:44 +0000 Subject: [PATCH] Construct effects from property lists The material code constructs a property list from its input parameters. Enable dumping of Pass and Technique objects to a file. Default effect now uses texture node instead of texture0 --- simgear/scene/material/Effect.cxx | 671 +++++++++++++++++++++++++- simgear/scene/material/Effect.hxx | 36 +- simgear/scene/material/Makefile.am | 1 + simgear/scene/material/Pass.cxx | 14 + simgear/scene/material/Technique.cxx | 28 +- simgear/scene/material/makeEffect.cxx | 190 ++++++++ simgear/scene/material/mat.cxx | 176 +++---- simgear/scene/material/mat.hxx | 46 +- simgear/scene/material/matlib.cxx | 19 +- 9 files changed, 1016 insertions(+), 165 deletions(-) create mode 100644 simgear/scene/material/makeEffect.cxx diff --git a/simgear/scene/material/Effect.cxx b/simgear/scene/material/Effect.cxx index 6e8fd931..c1db1d73 100644 --- a/simgear/scene/material/Effect.cxx +++ b/simgear/scene/material/Effect.cxx @@ -1,3 +1,19 @@ +// Copyright (C) 2008 - 2009 Tim Moore timoore@redhat.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + #include "Effect.hxx" #include "Technique.hxx" #include "Pass.hxx" @@ -5,24 +21,43 @@ #include #include #include +#include +#include #include #include +#include +#include +#include +#include #include +#include +#include +#include #include +#include #include +#include +#include +#include #include -#include +#include #include #include +#include +#include +#include +#include #include +#include namespace simgear { +using namespace std; using namespace osg; using namespace osgUtil; @@ -31,8 +66,8 @@ Effect::Effect() } Effect::Effect(const Effect& rhs, const CopyOp& copyop) + : root(rhs.root), parametersProp(rhs.parametersProp) { - using namespace std; using namespace boost; transform(rhs.techniques.begin(), rhs.techniques.end(), backRefInsertIterator(techniques), @@ -81,6 +116,638 @@ Effect::~Effect() { } +class PassAttributeBuilder : public Referenced +{ +public: + virtual void buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + = 0; +}; + +typedef map > PassAttrMap; +PassAttrMap passAttrMap; + +template +struct InstallAttributeBuilder +{ + InstallAttributeBuilder(const string& name) + { + passAttrMap.insert(make_pair(name, new T)); + } +}; +// Simple tables of strings and OSG constants. The table intialization +// *must* be in alphabetical order. +template +struct EffectNameValue +{ + // Don't use std::pair because we want to use aggregate intialization. + + const char* first; + T second; + class Compare + { + private: + static bool compare(const char* lhs, const char* rhs) + { + return strcmp(lhs, rhs) < 0; + } + public: + bool operator()(const EffectNameValue& lhs, + const EffectNameValue& rhs) const + { + return compare(lhs.first, rhs.first); + } + bool operator()(const char* lhs, const EffectNameValue& rhs) const + { + return compare(lhs, rhs.first); + } + bool operator()(const EffectNameValue& lhs, const char* rhs) const + { + return compare (lhs.first, rhs); + } + }; +}; + +template +bool findAttr(const ENV (&attrs)[N], const SGPropertyNode* prop, T& result) +{ + if (!prop) + return false; + const char* name = prop->getStringValue(); + if (!name) + return false; + std::pair itrs + = std::equal_range(&attrs[0], &attrs[N], name, typename ENV::Compare()); + if (itrs.first == itrs.second) { + return false; + } else { + result = itrs.first->second; + return true; + } +} + +void buildPass(Effect* effect, Technique* tniq, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) +{ + Pass* pass = new Pass; + tniq->passes.push_back(pass); + for (int i = 0; i < prop->nChildren(); ++i) { + const SGPropertyNode* attrProp = prop->getChild(i); + PassAttrMap::iterator itr = passAttrMap.find(attrProp->getName()); + if (itr != passAttrMap.end()) + itr->second->buildAttribute(effect, pass, attrProp, options); + else + SG_LOG(SG_INPUT, SG_ALERT, + "skipping unknown pass attribute " << attrProp->getName()); + } +} + +osg::Vec4f getColor(const SGPropertyNode* prop) +{ + if (prop->nChildren() == 0) { + if (prop->getType() == props::VEC4D) { + return osg::Vec4f(prop->getValue().osg()); + } else if (prop->getType() == props::VEC3D) { + return osg::Vec4f(prop->getValue().osg(), 1.0f); + } else { + SG_LOG(SG_INPUT, SG_ALERT, + "invalid color property " << prop->getName() << " " + << prop->getStringValue()); + return osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f); + } + } else { + osg::Vec4f result; + static const char* colors[] = {"r", "g", "b"}; + for (int i = 0; i < 3; ++i) { + const SGPropertyNode* componentProp = prop->getChild(colors[i]); + result[i] = componentProp ? componentProp->getValue() : 0.0f; + } + const SGPropertyNode* alphaProp = prop->getChild("a"); + result[3] = alphaProp ? alphaProp->getValue() : 1.0f; + return result; + } +} + +// Given a property node from a pass, get its value either from it or +// from the effect parameters. +const SGPropertyNode* getEffectPropertyNode(Effect* effect, + const SGPropertyNode* prop) +{ + if (!prop) + return 0; + if (prop->nChildren() > 0) { + const SGPropertyNode* useProp = prop->getChild("use"); + if (!useProp || !effect->parametersProp) + return prop; + return effect->parametersProp->getNode(useProp->getStringValue()); + } + return prop; +} + +// Get a named child property from pass parameters or effect +// parameters. +const SGPropertyNode* getEffectPropertyChild(Effect* effect, + const SGPropertyNode* prop, + const char* name) +{ + const SGPropertyNode* child = prop->getChild(name); + if (!child) + return 0; + else + return getEffectPropertyNode(effect, child); +} + +struct LightingBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options); +}; + +void LightingBuilder::buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) +{ + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + pass->setMode(GL_LIGHTING, (realProp->getValue() ? StateAttribute::ON + : StateAttribute::OFF)); +} + +InstallAttributeBuilder installLighting("lighting"); + +struct ShadeModelBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + StateAttributeFactory *attrFact = StateAttributeFactory::instance(); + string propVal = realProp->getStringValue(); + if (propVal == "flat") + pass->setAttribute(attrFact->getFlatShadeModel()); + else if (propVal == "smooth") + pass->setAttribute(attrFact->getSmoothShadeModel()); + else + SG_LOG(SG_INPUT, SG_ALERT, + "invalid shade model property " << propVal); + } +}; + +InstallAttributeBuilder installShadeModel("shade-model"); + +struct CullFaceBuilder : PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + StateAttributeFactory *attrFact = StateAttributeFactory::instance(); + string propVal = realProp->getStringValue(); + if (propVal == "front") + pass->setAttributeAndModes(attrFact->getCullFaceFront()); + else if (propVal == "back") + pass->setAttributeAndModes(attrFact->getCullFaceBack()); + else if (propVal == "front-back") + pass->setAttributeAndModes(new CullFace(CullFace::FRONT_AND_BACK)); + else + SG_LOG(SG_INPUT, SG_ALERT, + "invalid cull face property " << propVal); + } +}; + +InstallAttributeBuilder installCullFace("cull-face"); + +struct HintBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + string propVal = realProp->getStringValue(); + if (propVal == "opaque") + pass->setRenderingHint(StateSet::OPAQUE_BIN); + else if (propVal == "transparent") + pass->setRenderingHint(StateSet::TRANSPARENT_BIN); + } +}; + +InstallAttributeBuilder installHint("rendering-hint"); + +struct RenderBinBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* binProp = prop->getChild("bin-number"); + binProp = getEffectPropertyNode(effect, binProp); + const SGPropertyNode* nameProp = prop->getChild("bin-name"); + nameProp = getEffectPropertyNode(effect, nameProp); + if (binProp && nameProp) { + pass->setRenderBinDetails(binProp->getIntValue(), + nameProp->getStringValue()); + } else { + if (!binProp) + SG_LOG(SG_INPUT, SG_ALERT, + "No render bin number specified in render bin section"); + if (!nameProp) + SG_LOG(SG_INPUT, SG_ALERT, + "No render bin name specified in render bin section"); + } + } +}; + +InstallAttributeBuilder installRenderBin("render-bin"); + +struct MaterialBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options); +}; + +EffectNameValue colorModes[] = +{ + { "ambient", Material::AMBIENT }, + { "ambient-and-diffuse", Material::AMBIENT_AND_DIFFUSE }, + { "diffuse", Material::DIFFUSE }, + { "emissive", Material::EMISSION }, + { "specular", Material::SPECULAR }, + { "off", Material::OFF } +}; + +void MaterialBuilder::buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) +{ + Material* mat = new Material; + const SGPropertyNode* color = 0; + if ((color = getEffectPropertyChild(effect, prop, "ambient"))) + mat->setAmbient(Material::FRONT_AND_BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "ambient-front"))) + mat->setAmbient(Material::FRONT, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "ambient-back"))) + mat->setAmbient(Material::BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "diffuse"))) + mat->setDiffuse(Material::FRONT_AND_BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "diffuse-front"))) + mat->setDiffuse(Material::FRONT, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "diffuse-back"))) + mat->setDiffuse(Material::BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "specular"))) + mat->setSpecular(Material::FRONT_AND_BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "specular-front"))) + mat->setSpecular(Material::FRONT, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "specular-back"))) + mat->setSpecular(Material::BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "emissive"))) + mat->setEmission(Material::FRONT_AND_BACK, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "emissive-front"))) + mat->setEmission(Material::FRONT, getColor(color)); + if ((color = getEffectPropertyChild(effect, prop, "emissive-back"))) + mat->setEmission(Material::BACK, getColor(color)); + const SGPropertyNode* shininess = 0; + if ((shininess = getEffectPropertyChild(effect, prop, "shininess"))) + mat->setShininess(Material::FRONT_AND_BACK, shininess->getFloatValue()); + if ((shininess = getEffectPropertyChild(effect, prop, "shininess-front"))) + mat->setShininess(Material::FRONT, shininess->getFloatValue()); + if ((shininess = getEffectPropertyChild(effect, prop, "shininess-back"))) + mat->setShininess(Material::BACK, shininess->getFloatValue()); + Material::ColorMode colorMode = Material::OFF; + findAttr(colorModes, getEffectPropertyChild(effect, prop, "color-mode"), + colorMode); + mat->setColorMode(colorMode); + pass->setAttribute(mat); +} + +InstallAttributeBuilder installMaterial("material"); + +struct BlendBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + pass->setMode(GL_BLEND, (realProp->getBoolValue() + ? StateAttribute::ON + : StateAttribute::OFF)); + } +}; + +InstallAttributeBuilder installBlend("blend"); + +struct AlphaTestBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + pass->setMode(GL_ALPHA_TEST, (realProp->getBoolValue() + ? StateAttribute::ON + : StateAttribute::OFF)); + } +}; + +InstallAttributeBuilder installAlphaTest("alpha-test"); + +EffectNameValue filterModes[] = +{ + { "linear", Texture::LINEAR }, + { "linear-mipmap-linear", Texture::LINEAR_MIPMAP_LINEAR}, + { "linear-mipmap-nearest", Texture::LINEAR_MIPMAP_NEAREST}, + { "nearest", Texture::NEAREST}, + { "nearest-mipmap-linear", Texture::NEAREST_MIPMAP_LINEAR}, + { "nearest-mipmap-nearest", Texture::NEAREST_MIPMAP_NEAREST} +}; + +EffectNameValue wrapModes[] = +{ + {"clamp", Texture::CLAMP}, + {"clamp-to-border", Texture::CLAMP_TO_BORDER}, + {"clamp-to-edge", Texture::CLAMP_TO_EDGE}, + {"mirror", Texture::MIRROR}, + {"repeat", Texture::REPEAT} +}; + +EffectNameValue texEnvModes[] = +{ + {"add", TexEnv::ADD}, + {"blend", TexEnv::BLEND}, + {"decal", TexEnv::DECAL}, + {"modulate", TexEnv::MODULATE}, + {"replace", TexEnv::REPLACE} +}; + +TexEnv* buildTexEnv(Effect* effect, const SGPropertyNode* prop) +{ + const SGPropertyNode* modeProp = getEffectPropertyChild(effect, prop, + "mode"); + const SGPropertyNode* colorProp = getEffectPropertyChild(effect, prop, + "color"); + if (!modeProp) + return 0; + TexEnv::Mode mode = TexEnv::MODULATE; + findAttr(texEnvModes, modeProp, mode); + if (mode == TexEnv::MODULATE) { + return StateAttributeFactory::instance()->getStandardTexEnv(); + } + TexEnv* env = new TexEnv(mode); + if (colorProp) + env->setColor(colorProp->getValue().osg()); + return env; + } + +typedef boost::tuple TexTuple; + +typedef map > TexMap; + +TexMap texMap; + +struct TextureUnitBuilder : PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options); +}; + +void TextureUnitBuilder::buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) +{ + // First, all the texture properties + const SGPropertyNode* pTexture2d = prop->getChild("texture2d"); + if (!pTexture2d) + return; + const SGPropertyNode* pImage + = getEffectPropertyChild(effect, pTexture2d, "image"); + if (!pImage) + return; + const char* imageName = pImage->getStringValue(); + Texture::FilterMode minFilter = Texture::LINEAR_MIPMAP_LINEAR; + findAttr(filterModes, getEffectPropertyChild(effect, pTexture2d, "filter"), + minFilter); + Texture::FilterMode magFilter = Texture::LINEAR; + findAttr(filterModes, getEffectPropertyChild(effect, pTexture2d, + "mag-filter"), + magFilter); + const SGPropertyNode* pWrapS + = getEffectPropertyChild(effect, pTexture2d, "wrap-s"); + Texture::WrapMode sWrap = Texture::CLAMP; + findAttr(wrapModes, pWrapS, sWrap); + const SGPropertyNode* pWrapT + = getEffectPropertyChild(effect, pTexture2d, "wrap-t"); + Texture::WrapMode tWrap = Texture::CLAMP; + findAttr(wrapModes, pWrapT, tWrap); + const SGPropertyNode* pWrapR + = getEffectPropertyChild(effect, pTexture2d, "wrap-r"); + Texture::WrapMode rWrap = Texture::CLAMP; + findAttr(wrapModes, pWrapR, rWrap); + TexTuple tuple(imageName, minFilter, magFilter, sWrap, tWrap, rWrap); + TexMap::iterator texIter = texMap.find(tuple); + Texture2D* texture = 0; + if (texIter != texMap.end()) { + texture = texIter->second.get(); + } else { + texture = new Texture2D; + osgDB::ReaderWriter::ReadResult result + = osgDB::Registry::instance()->readImage(imageName, options); + if (result.success()) { + osg::Image* image = result.getImage(); + texture->setImage(image); + int s = image->s(); + int t = image->t(); + + if (s <= t && 32 <= s) { + SGSceneFeatures::instance()->setTextureCompression(texture); + } else if (t < s && 32 <= t) { + SGSceneFeatures::instance()->setTextureCompression(texture); + } + texture->setMaxAnisotropy(SGSceneFeatures::instance() + ->getTextureFilter()); + } else { + SG_LOG(SG_INPUT, SG_ALERT, "failed to load effect texture file " + << imageName); + } + // texture->setDataVariance(osg::Object::STATIC); + texture->setFilter(Texture::MIN_FILTER, minFilter); + texture->setFilter(Texture::MAG_FILTER, magFilter); + texture->setWrap(Texture::WRAP_S, sWrap); + texture->setWrap(Texture::WRAP_T, tWrap); + texture->setWrap(Texture::WRAP_R, rWrap); + texMap.insert(make_pair(tuple, texture)); + } + // Decode the texture unit + int unit = 0; + const SGPropertyNode* pUnit = prop->getChild("unit"); + if (pUnit) { + unit = pUnit->getValue(); + } else { + const SGPropertyNode* pName = prop->getChild("name"); + if (pName) + try { + unit = boost::lexical_cast(pName->getStringValue()); + } catch (boost::bad_lexical_cast& lex) { + SG_LOG(SG_INPUT, SG_ALERT, "can't decode name as texture unit " + << lex.what()); + } + } + pass->setTextureAttributeAndModes(unit, texture); + const SGPropertyNode* envProp = prop->getChild("environment"); + if (envProp) { + TexEnv* env = buildTexEnv(effect, envProp); + if (env) + pass->setTextureAttributeAndModes(unit, env); + } +} + +InstallAttributeBuilder textureUnitBuilder("texture-unit"); + +typedef map > ProgramMap; +ProgramMap programMap; + +typedef map > ShaderMap; +ShaderMap shaderMap; + +struct ShaderProgramBuilder : PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options); +}; + +void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* + options) +{ + PropertyList pVertShaders = prop->getChildren("vertex-shader"); + PropertyList pFragShaders = prop->getChildren("fragment-shader"); + string programKey; + for (PropertyList::iterator itr = pVertShaders.begin(), + e = pVertShaders.end(); + itr != e; + ++itr) + { + programKey += (*itr)->getStringValue(); + programKey += ";"; + } + for (PropertyList::iterator itr = pFragShaders.begin(), + e = pFragShaders.end(); + itr != e; + ++itr) + { + programKey += (*itr)->getStringValue(); + programKey += ";"; + } + Program* program = 0; + ProgramMap::iterator pitr = programMap.find(programKey); + if (pitr != programMap.end()) { + program = pitr->second.get(); + } else { + program = new Program; + // Add vertex shaders, then fragment shaders + PropertyList& pvec = pVertShaders; + Shader::Type stype = Shader::VERTEX; + for (int i = 0; i < 2; ++i) { + for (PropertyList::iterator nameItr = pvec.begin(), e = pvec.end(); + nameItr != e; + ++nameItr) { + string shaderName = (*nameItr)->getStringValue(); + ShaderMap::iterator sitr = shaderMap.find(shaderName); + if (sitr != shaderMap.end()) { + program->addShader(sitr->second.get()); + } else { + string fileName = osgDB::findDataFile(shaderName, options); + if (!fileName.empty()) { + ref_ptr shader = new Shader(stype); + if (shader->loadShaderSourceFromFile(fileName)) { + shaderMap.insert(make_pair(shaderName, shader)); + program->addShader(shader.get()); + } + } + } + } + pvec = pFragShaders; + stype = Shader::FRAGMENT; + } + programMap.insert(make_pair(programKey, program)); + } + pass->setAttributeAndModes(program); +} + +// Not sure what to do with "name". At one point I wanted to use it to +// order the passes, but I do support render bin and stuff too... + +struct NameBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + // name can't use + string name = prop->getStringValue(); + if (!name.empty()) + pass->setName(name); + } +}; + +InstallAttributeBuilder installName("name"); + +void buildTechnique(Effect* effect, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) +{ + Technique* tniq = new Technique; + effect->techniques.push_back(tniq); + const SGPropertyNode* predProp = prop->getChild("predicate"); + if (!predProp) { + tniq->setAlwaysValid(true); + } else { + try { + expression::BindingLayout layout; + int contextLoc = layout.addBinding("__contextId", expression::INT); + SGExpressionb* validExp + = dynamic_cast(expression::read(predProp + ->getChild(0))); + if (validExp) + tniq->setValidExpression(validExp, layout); + else + throw expression::ParseError("technique predicate is not a boolean expression"); + } + catch (expression::ParseError& except) + { + SG_LOG(SG_INPUT, SG_ALERT, + "parsing technique predicate " << except.getMessage()); + tniq->setAlwaysValid(false); + } + } + PropertyList passProps = prop->getChildren("pass"); + for (PropertyList::iterator itr = passProps.begin(), e = passProps.end(); + itr != e; + ++itr) { + buildPass(effect, tniq, itr->ptr(), options); + } +} + +// Walk the techniques property tree, building techniques and +// passes. +bool Effect::realizeTechniques(const osgDB::ReaderWriter::Options* options) +{ + PropertyList tniqList = root->getChildren("technique"); + for (PropertyList::iterator itr = tniqList.begin(), e = tniqList.end(); + itr != e; + ++itr) + buildTechnique(this, *itr, options); +} + bool Effect_writeLocalData(const Object& obj, osgDB::Output& fw) { const Effect& effect = static_cast(obj); diff --git a/simgear/scene/material/Effect.hxx b/simgear/scene/material/Effect.hxx index 8c86d945..2a3c896e 100644 --- a/simgear/scene/material/Effect.hxx +++ b/simgear/scene/material/Effect.hxx @@ -1,26 +1,29 @@ -// Copyright (C) 2008 Timothy Moore timoore@redhat.com +// Copyright (C) 2008 - 2009 Tim Moore timoore@redhat.com // -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. // -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. +// Library General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - #ifndef SIMGEAR_EFFECT_HXX #define SIMGEAR_EFFECT_HXX 1 #include +#include #include +#include + +#include namespace osg { @@ -47,11 +50,26 @@ public: const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); osg::StateSet* getDefaultStateSet(); std::vector > techniques; + SGPropertyNode_ptr root; + // Pointer to the parameters node, if it exists + SGPropertyNode_ptr parametersProp; Technique* chooseTechnique(osg::RenderInfo* renderInfo); virtual void resizeGLObjectBuffers(unsigned int maxSize); virtual void releaseGLObjects(osg::State* state = 0) const; + /* + * Build the techniques from the effect properties. + */ + bool realizeTechniques(const osgDB::ReaderWriter::Options* options = 0); + protected: ~Effect(); }; +Effect* makeEffect(const std::string& name, + bool realizeTechniques, + const osgDB::ReaderWriter::Options* options = 0); + +Effect* makeEffect(SGPropertyNode* prop, + bool realizeTechniques, + const osgDB::ReaderWriter::Options* options = 0); } #endif diff --git a/simgear/scene/material/Makefile.am b/simgear/scene/material/Makefile.am index eca5aa47..aee10b42 100644 --- a/simgear/scene/material/Makefile.am +++ b/simgear/scene/material/Makefile.am @@ -22,6 +22,7 @@ libsgmaterial_a_SOURCES = \ GLPredicate.cxx \ Pass.cxx \ Technique.cxx \ + makeEffect.cxx \ mat.cxx \ matlib.cxx \ matmodel.cxx diff --git a/simgear/scene/material/Pass.cxx b/simgear/scene/material/Pass.cxx index 9c1aafce..2a47ea39 100644 --- a/simgear/scene/material/Pass.cxx +++ b/simgear/scene/material/Pass.cxx @@ -1,5 +1,7 @@ #include "Pass.hxx" +#include + namespace simgear { @@ -7,4 +9,16 @@ Pass::Pass(const Pass& rhs, const osg::CopyOp& copyop) : osg::StateSet(rhs, copyop) { } + +namespace +{ +osgDB::RegisterDotOsgWrapperProxy PassProxy +( + new Pass, + "simgear::Pass", + "Object simgear::Pass StateSet ", + 0, + 0 + ); +} } diff --git a/simgear/scene/material/Technique.cxx b/simgear/scene/material/Technique.cxx index 5c1d835b..19699607 100644 --- a/simgear/scene/material/Technique.cxx +++ b/simgear/scene/material/Technique.cxx @@ -15,6 +15,7 @@ #include #include +#include #include namespace simgear @@ -217,6 +218,14 @@ public: } }; +Expression* glVersionParser(const SGPropertyNode* exp, + expression::Parser* parser) +{ + return new GLVersionExpression(); +} + +expression::ExpParserRegistrar glVersionRegistrar("glversion", glVersionParser); + class ExtensionSupportedExpression : public GeneralNaryExpression { @@ -237,6 +246,18 @@ protected: string _extString; }; +Expression* extensionSupportedParser(const SGPropertyNode* exp, + expression::Parser* parser) +{ + if (exp->getType() == props::STRING + || exp->getType() == props::UNSPECIFIED) + return new ExtensionSupportedExpression(exp->getStringValue()); + throw expression::ParseError("extension-supported expression has wrong type"); +} + +expression::ExpParserRegistrar +extensionSupportedRegistrar("extension-supported", extensionSupportedParser); + void Technique::setGLExtensionsPred(float glVersion, const std::vector& extensions) { @@ -246,9 +267,14 @@ void Technique::setGLExtensionsPred(float glVersion, int contextLoc = layout.addBinding("__contextId", INT); VariableExpression* contextExp = new VariableExpression(contextLoc); + SGExpression* versionTest + = makePredicate(new SGConstExpression(glVersion), + new GLVersionExpression); +#if 0 LessEqualExpression* versionTest = new LessEqualExpression(new SGConstExpression(glVersion), new GLVersionExpression); +#endif AndExpression* extensionsExp = 0; for (vector::const_iterator itr = extensions.begin(), e = extensions.end(); @@ -285,7 +311,7 @@ bool Technique_writeLocalData(const Object& obj, osgDB::Output& fw) fw.indent() << "shadowingStateSet\n"; fw.writeObject(*tniq.getShadowingStateSet()); } - fw.indent() << "passes\n"; + fw.indent() << "num_passes " << tniq.passes.size() << "\n"; BOOST_FOREACH(const ref_ptr& pass, tniq.passes) { fw.writeObject(*pass); } diff --git a/simgear/scene/material/makeEffect.cxx b/simgear/scene/material/makeEffect.cxx new file mode 100644 index 00000000..7745cd97 --- /dev/null +++ b/simgear/scene/material/makeEffect.cxx @@ -0,0 +1,190 @@ +#include "Effect.hxx" +#include "Technique.hxx" +#include "Pass.hxx" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace simgear +{ +using namespace std; +using namespace osg; + +typedef vector RawPropVector; +typedef map > EffectMap; + +namespace +{ +EffectMap effectMap; +OpenThreads::ReentrantMutex effectMutex; +} + +/** Merge two property trees, producing a new tree. + * If the nodes are both leaves, value comes from left leaf. + * Otherwise, The children are examined. If a left and right child are + * "identical," they are merged and the result placed in the children + * of the result. Otherwise the left children are placed after the + * right children in the result. + * + * Nodes are considered identical if: + * Their names are equal; + * Either they both have "name" children and their values are equal; + * or their indexes are equal. + */ + +struct PropPredicate + : public unary_function +{ + PropPredicate(const SGPropertyNode* node_) : node(node_) {} + bool operator()(const SGPropertyNode* arg) const + { + if (strcmp(node->getName(), arg->getName())) + return false; + const SGPropertyNode* nodeName = node->getChild("name"); + const SGPropertyNode* argName = arg->getChild("name"); + if (nodeName && argName) + return !strcmp(nodeName->getStringValue(), + argName->getStringValue()); + else if (!(nodeName || argName)) + return node->getIndex() == arg->getIndex(); + else + return false; + } + const SGPropertyNode* node; +}; + +void mergePropertyTrees(SGPropertyNode* resultNode, + const SGPropertyNode* left, const SGPropertyNode* right) +{ + if (left->nChildren() == 0) { + copyProperties(left, resultNode); + return; + } + resultNode->setAttributes(right->getAttributes()); + RawPropVector leftChildren; + for (int i = 0; i < left->nChildren(); ++i) + leftChildren.push_back(left->getChild(i)); + // Maximum index of nodes (with same names) we've created. + map nodeIndex; + // Merge identical nodes + for (int i = 0; i < right->nChildren(); ++i) { + const SGPropertyNode* node = right->getChild(i); + RawPropVector::iterator litr + = find_if(leftChildren.begin(), leftChildren.end(), + PropPredicate(node)); + SGPropertyNode* newChild + = resultNode->getChild(node->getName(), + nodeIndex[node->getName()]++, true); + if (litr != leftChildren.end()) { + mergePropertyTrees(newChild, *litr, node); + leftChildren.erase(litr); + } else { + copyProperties(node, newChild); + } + } + for (RawPropVector::iterator itr = leftChildren.begin(), + e = leftChildren.end(); + itr != e; + ++itr) { + SGPropertyNode* newChild + = resultNode->getChild((*itr)->getName(), + nodeIndex[(*itr)->getName()]++, true); + copyProperties(*itr, newChild); + } +} + +Effect* makeEffect(const string& name, + bool realizeTechniques, + const osgDB::ReaderWriter::Options* options) +{ + OpenThreads::ScopedLock lock(effectMutex); + EffectMap::iterator itr = effectMap.find(name); + if (itr != effectMap.end()) + return itr->second.get(); + string effectFileName(name); + effectFileName += ".eff"; + string absFileName + = osgDB::Registry::instance()->findDataFile(effectFileName, options, + osgDB::CASE_SENSITIVE); + if (absFileName.empty()) + return 0; + SGPropertyNode_ptr effectProps = new SGPropertyNode(); + readProperties(absFileName, effectProps.ptr(), 0, true); + Effect* result = makeEffect(effectProps.ptr(), realizeTechniques, options); + if (result) + effectMap.insert(make_pair(name, result)); + return result; +} + + +Effect* makeEffect(SGPropertyNode* prop, + bool realizeTechniques, + const osgDB::ReaderWriter::Options* options) +{ + // Give default names to techniques and passes + vector techniques = prop->getChildren("technique"); + for (int i = 0; i < techniques.size(); ++i) { + SGPropertyNode* tniqProp = techniques[i].ptr(); + if (!tniqProp->hasChild("name")) + setValue(tniqProp->getChild("name", 0, true), + boost::lexical_cast(i)); + vector passes = tniqProp->getChildren("pass"); + for (int j = 0; j < passes.size(); ++j) { + SGPropertyNode* passProp = passes[j].ptr(); + if (!passProp->hasChild("name")) + setValue(passProp->getChild("name", 0, true), + boost::lexical_cast(j)); + vector texUnits + = passProp->getChildren("texture-unit"); + for (int k = 0; k < texUnits.size(); ++k) { + SGPropertyNode* texUnitProp = texUnits[k].ptr(); + if (!texUnitProp->hasChild("name")) + setValue(texUnitProp->getChild("name", 0, true), + boost::lexical_cast(k)); + } + } + } + Effect* effect = new Effect; + // Merge with the parent effect, if any + const SGPropertyNode* inheritProp = prop->getChild("inherits-from"); + Effect* parent = 0; + if (inheritProp) { + parent = makeEffect(inheritProp->getStringValue(), realizeTechniques, + options); + effect->root = new SGPropertyNode; + mergePropertyTrees(effect->root, prop, parent->root); + effect->root->removeChild("inherits-from"); + } else { + effect->root = prop; + } + effect->parametersProp = effect->root->getChild("parameters"); + if (realizeTechniques) + effect->realizeTechniques(options); + return effect; +} + +} diff --git a/simgear/scene/material/mat.cxx b/simgear/scene/material/mat.cxx index 7a0628f0..1484c420 100644 --- a/simgear/scene/material/mat.cxx +++ b/simgear/scene/material/mat.cxx @@ -32,6 +32,7 @@ #include #include +#include #include "mat.hxx" #include @@ -40,14 +41,17 @@ #include #include #include +#include #include +#include #include #include #include #include - +#include #include +#include #include #include "Effect.hxx" @@ -55,6 +59,7 @@ #include "Pass.hxx" using std::map; +using std::string; using namespace simgear; @@ -62,33 +67,18 @@ using namespace simgear; // Constructors and destructor. //////////////////////////////////////////////////////////////////////// -SGMaterial::_internal_state::_internal_state(osg::StateSet *s, - const std::string &t, bool l ) : - state(s), texture_path(t), texture_loaded(l) +SGMaterial::_internal_state::_internal_state(Effect *e, const string &t, bool l, + const osgDB::Options* o ) : + effect(e), texture_path(t), effect_realized(l), options(o) { } -SGMaterial::SGMaterial( const string &fg_root, const SGPropertyNode *props ) +SGMaterial::SGMaterial( const osgDB::Options* options, + const SGPropertyNode *props ) { init(); - read_properties( fg_root, props ); - build_state( false ); -} - -SGMaterial::SGMaterial( const string &texpath ) -{ - init(); - - _internal_state st( NULL, texpath, false ); - _status.push_back( st ); - - build_state( true ); -} - -SGMaterial::SGMaterial( osg::StateSet *s ) -{ - init(); - set_state( s ); + read_properties( options, props ); + buildEffectProperties(options); } SGMaterial::~SGMaterial (void) @@ -101,7 +91,8 @@ SGMaterial::~SGMaterial (void) //////////////////////////////////////////////////////////////////////// void -SGMaterial::read_properties( const string &fg_root, const SGPropertyNode *props) +SGMaterial::read_properties(const osgDB::Options* options, + const SGPropertyNode *props) { // Gather the path(s) to the texture(s) vector textures = props->getChildren("texture"); @@ -111,29 +102,29 @@ SGMaterial::read_properties( const string &fg_root, const SGPropertyNode *props) if (tname.empty()) { tname = "unknown.rgb"; } - - SGPath tpath( fg_root ); - tpath.append("Textures.high"); + SGPath tpath("Textures.high"); tpath.append(tname); - if ( !osgDB::fileExists(tpath.str()) ) { - tpath = SGPath( fg_root ); - tpath.append("Textures"); + osgDB::Registry* reg = osgDB::Registry::instance(); + string fullTexPath = reg->findDataFile(tpath.str(), options, + osgDB::CASE_SENSITIVE); + if (fullTexPath.empty()) { + tpath = SGPath("Textures"); tpath.append(tname); + fullTexPath = reg->findDataFile(tpath.str(), options, + osgDB::CASE_SENSITIVE); } - if ( osgDB::fileExists(tpath.str()) ) { - _internal_state st( NULL, tpath.str(), false ); + if (!fullTexPath.empty() ) { + _internal_state st( NULL, fullTexPath, false, options ); _status.push_back( st ); } } if (textures.size() == 0) { - string tname = "unknown.rgb"; - SGPath tpath( fg_root ); - tpath.append("Textures"); + SGPath tpath("Textures"); tpath.append("Terrain"); - tpath.append(tname); - _internal_state st( NULL, tpath.str(), true ); + tpath.append("unknown.rgb"); + _internal_state st( NULL, tpath.str(), true, options ); _status.push_back( st ); } @@ -148,10 +139,12 @@ SGMaterial::read_properties( const string &fg_root, const SGPropertyNode *props) tree_width = props->getDoubleValue("tree-width-m", 0.0); tree_range = props->getDoubleValue("tree-range-m", 0.0); tree_varieties = props->getIntValue("tree-varieties", 1); - - SGPath tpath( fg_root ); - tpath.append(props->getStringValue("tree-texture")); - tree_texture = tpath.str(); + const SGPropertyNode* treeTexNode = props->getChild("tree-texture"); + if (treeTexNode) { + string treeTexPath = props->getStringValue("tree-texture"); + tree_texture = osgDB::Registry::instance() + ->findDataFile(treeTexPath, options, osgDB::CASE_SENSITIVE); + } // surface values for use with ground reactions solid = props->getBoolValue("solid", true); @@ -238,10 +231,9 @@ Effect* SGMaterial::get_effect(int n) return 0; } int i = n >= 0 ? n : _current_ptr; - if(!_status[i].texture_loaded) { - assignTexture(_status[i].state.get(), _status[i].texture_path, - wrapu, wrapv, mipmap); - _status[i].texture_loaded = true; + if(!_status[i].effect_realized) { + _status[i].effect->realizeTechniques(_status[i].options.get()); + _status[i].effect_realized = true; } // XXX This business of returning a "random" alternate texture is // really bogus. It means that the appearance of the terrain @@ -250,72 +242,44 @@ Effect* SGMaterial::get_effect(int n) return _status[i].effect.get(); } -void -SGMaterial::build_state( bool defer_tex_load ) +void SGMaterial::buildEffectProperties(const osgDB::Options* options) { - StateAttributeFactory *attrFact = StateAttributeFactory::instance(); - SGMaterialUserData* user = new SGMaterialUserData(this); - for (unsigned int i = 0; i < _status.size(); i++) + using namespace osg; + SGPropertyNode_ptr propRoot = new SGPropertyNode(); + makeChild(propRoot, "inherits-from") + ->setStringValue("Effects/terrain-default"); + SGPropertyNode* paramProp = makeChild(propRoot, "parameters"); + SGPropertyNode* materialProp = makeChild(paramProp, "material"); + makeChild(materialProp, "ambient")->setValue(SGVec4d(ambient)); + makeChild(materialProp, "diffuse")->setValue(SGVec4d(diffuse)); + makeChild(materialProp, "specular")->setValue(SGVec4d(specular)); + makeChild(materialProp, "emissive")->setValue(SGVec4d(emission)); + makeChild(materialProp, "shininess")->setFloatValue(shininess); + if (ambient[3] < 1 || diffuse[3] < 1 || + specular[3] < 1 || emission[3] < 1) { + makeChild(paramProp, "transparent")->setBoolValue(true); + SGPropertyNode* binProp = makeChild(paramProp, "render-bin"); + makeChild(binProp, "bin-number")->setIntValue(TRANSPARENT_BIN); + makeChild(binProp, "bin-name")->setStringValue("DepthSortedBin"); + } + BOOST_FOREACH(_internal_state& matState, _status) { - Pass *pass = new Pass; - pass->setUserData(user); - - // Set up the textured state - pass->setAttribute(attrFact->getSmoothShadeModel()); - pass->setAttributeAndModes(attrFact->getCullFaceBack()); - - pass->setMode(GL_LIGHTING, osg::StateAttribute::ON); - - _status[i].texture_loaded = false; - - osg::Material* material = new osg::Material; - material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - material->setAmbient(osg::Material::FRONT_AND_BACK, ambient.osg()); - material->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse.osg()); - material->setSpecular(osg::Material::FRONT_AND_BACK, specular.osg()); - material->setEmission(osg::Material::FRONT_AND_BACK, emission.osg()); - material->setShininess(osg::Material::FRONT_AND_BACK, shininess ); - pass->setAttribute(material); - - if (ambient[3] < 1 || diffuse[3] < 1 || - specular[3] < 1 || emission[3] < 1) { - pass->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); - pass->setMode(GL_BLEND, osg::StateAttribute::ON); - pass->setMode(GL_ALPHA_TEST, osg::StateAttribute::ON); - } else { - pass->setRenderingHint(osg::StateSet::OPAQUE_BIN); - pass->setMode(GL_BLEND, osg::StateAttribute::OFF); - pass->setMode(GL_ALPHA_TEST, osg::StateAttribute::OFF); - } - - _status[i].state = pass; - Technique* tniq = new Technique(true); - tniq->passes.push_back(pass); - Effect* effect = new Effect; - effect->techniques.push_back(tniq); - effect->setUserData(user); - _status[i].effect = effect; + SGPropertyNode_ptr effectProp = new SGPropertyNode(); + copyProperties(propRoot, effectProp); + SGPropertyNode* effectParamProp = effectProp->getChild("parameters", 0); + SGPropertyNode* texProp = makeChild(effectParamProp, "texture"); + SGPropertyNode* tex2dProp = makeChild(texProp, "texture2d"); + makeChild(tex2dProp, "image")->setStringValue(matState.texture_path); + makeChild(tex2dProp, "filter") + ->setStringValue(mipmap ? "linear-mipmap-linear" : "nearest"); + makeChild(tex2dProp, "wrap-s") + ->setStringValue(wrapu ? "repeat" : "clamp"); + makeChild(tex2dProp, "wrap-t") + ->setStringValue(wrapv ? "repeat" : "clamp"); + matState.effect = makeEffect(effectProp, false, options); } } - -void SGMaterial::set_state( osg::StateSet *s ) -{ - _status.push_back( _internal_state( s, "", true ) ); -} - -void SGMaterial::assignTexture( osg::StateSet *state, const std::string &fname, - bool _wrapu, bool _wrapv, bool _mipmap ) -{ - osg::Texture2D* texture = SGLoadTexture2D(fname, 0, _wrapu, _wrapv, - mipmap ? -1 : 0); - texture->setMaxAnisotropy( SGGetTextureFilter()); - state->setTextureAttributeAndModes(0, texture); - - StateAttributeFactory *attrFact = StateAttributeFactory::instance(); - state->setTextureAttributeAndModes(0, attrFact->getStandardTexEnv()); -} - SGMaterialGlyph* SGMaterial::get_glyph (const string& name) const { map >::const_iterator it; diff --git a/simgear/scene/material/mat.hxx b/simgear/scene/material/mat.hxx index e041b698..1317072a 100644 --- a/simgear/scene/material/mat.hxx +++ b/simgear/scene/material/mat.hxx @@ -45,6 +45,11 @@ namespace osg class StateSet; } +namespace osgDB +{ +class Options; +} + #include #include #include @@ -83,28 +88,7 @@ public: * state information for the material. This node is usually * loaded from the $FG_ROOT/materials.xml file. */ - SGMaterial( const std::string &fg_root, const SGPropertyNode *props); - - - /** - * Construct a material from an absolute texture path. - * - * @param texture_path A string containing an absolute path - * to a texture file (usually RGB). - */ - SGMaterial( const std::string &texpath ); - - - /** - * Construct a material around an existing state. - * - * This constructor allows the application to create a custom, - * low-level state for the scene graph and wrap a material around - * it. Note: the pointer ownership is transferred to the material. - * - * @param s The state for this material. - */ - SGMaterial( osg::StateSet *s ); + SGMaterial( const osgDB::Options*, const SGPropertyNode *props); /** * Destructor. @@ -275,11 +259,12 @@ protected: protected: struct _internal_state { - _internal_state( osg::StateSet *s, const std::string &t, bool l ); - osg::ref_ptr state; + _internal_state(simgear::Effect *e, const std::string &t, bool l, + const osgDB::Options *o); osg::ref_ptr effect; std::string texture_path; - bool texture_loaded; + bool effect_realized; + osg::ref_ptr options; }; private: @@ -356,14 +341,9 @@ private: // Internal constructors and methods. //////////////////////////////////////////////////////////////////// - SGMaterial( const std::string &fg_root, const SGMaterial &mat ); // unimplemented - - void read_properties( const std::string &fg_root, const SGPropertyNode *props ); - void build_state( bool defer_tex_load ); - void set_state( osg::StateSet *s ); - - void assignTexture( osg::StateSet *state, const std::string &fname, bool _wrapu = true, bool _wrapv = true, bool _mipmap = true ); - + void read_properties(const osgDB::Options* options, + const SGPropertyNode *props); + void buildEffectProperties(const osgDB::Options* options); }; diff --git a/simgear/scene/material/matlib.cxx b/simgear/scene/material/matlib.cxx index 4f135770..c4aa8a8f 100644 --- a/simgear/scene/material/matlib.cxx +++ b/simgear/scene/material/matlib.cxx @@ -36,18 +36,7 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include #include @@ -82,7 +71,9 @@ bool SGMaterialLib::load( const string &fg_root, const string& mpath, << ex.getMessage() ); throw; } - + osg::ref_ptr options = new osgDB::Options; + options->setObjectCacheHint(osgDB::Options::CACHE_ALL); + options->setDatabasePath(fg_root); int nMaterials = materials.nChildren(); for (int i = 0; i < nMaterials; i++) { const SGPropertyNode *node = materials.getChild(i); @@ -97,7 +88,7 @@ bool SGMaterialLib::load( const string &fg_root, const string& mpath, } } - SGSharedPtr m = new SGMaterial(fg_root, node); + SGSharedPtr m = new SGMaterial(options.get(), node); vectornames = node->getChildren("name"); for ( unsigned int j = 0; j < names.size(); j++ ) { -- 2.39.5