#include "SGMaterialAnimation.hxx"
#include <osg/AlphaFunc>
+#include <osg/Array>
#include <osg/Drawable>
#include <osg/Geode>
#include <osg/Geometry>
+#include <osg/Material>
#include <osg/StateSet>
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <simgear/props/props.hxx>
#include <simgear/scene/model/model.hxx>
-struct SGMaterialAnimation::ColorSpec {
+namespace {
+/**
+ * Get a color from properties.
+ */
+struct ColorSpec {
float red, green, blue;
float factor;
float offset;
v[3] = 1;
return v;
}
+
+ osg::Vec4& rgbaVec4()
+ {
+ return rgba().osg();
+ }
+
SGVec4f &initialRgba() {
v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
}
};
-
-struct SGMaterialAnimation::PropSpec {
+/**
+ * Get a property value from a property.
+ */
+struct PropSpec {
float value;
float factor;
float offset;
}
};
-class SGMaterialAnimation::MaterialVisitor : public osg::NodeVisitor {
-public:
- enum {
- DIFFUSE = 1,
- AMBIENT = 2,
- SPECULAR = 4,
- EMISSION = 8,
- SHININESS = 16,
- TRANSPARENCY = 32
- };
+/**
+ * The possible color properties supplied by a material animation.
+ */
+enum SuppliedColor {
+ DIFFUSE = 1,
+ AMBIENT = 2,
+ SPECULAR = 4,
+ EMISSION = 8,
+ SHININESS = 16,
+ TRANSPARENCY = 32
+};
- MaterialVisitor() :
- osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
- _updateMask(0),
- _ambient(-1, -1, -1, -1),
- _diffuse(-1, -1, -1, -1),
- _specular(-1, -1, -1, -1),
- _emission(-1, -1, -1, -1),
- _shininess(-1),
- _alpha(-1)
- {
- setVisitorType(osg::NodeVisitor::NODE_VISITOR);
- }
+const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
- void setDiffuse(const SGVec4f& diffuse)
- {
- if (diffuse != _diffuse)
- _diffuse = diffuse;
- _updateMask |= DIFFUSE;
- }
- void setAmbient(const SGVec4f& ambient)
- {
- if (ambient != _ambient)
- _ambient = ambient;
- _updateMask |= AMBIENT;
- }
- void setSpecular(const SGVec4f& specular)
- {
- if (specular != _specular)
- _specular = specular;
- _updateMask |= SPECULAR;
- }
- void setEmission(const SGVec4f& emission)
- {
- if (emission != _emission)
- _emission = emission;
- _updateMask |= EMISSION;
- }
- void setShininess(float shininess)
- {
- if (shininess != _shininess)
- _shininess = shininess;
- _updateMask |= SHININESS;
- }
+const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
+ | SHININESS);
- void setAlpha(float alpha)
+// Visitor for finding default material colors in the animation node's
+// subgraph. This makes some assumptions about the subgraph i.e.,
+// there will be one material and one color value found. This is
+// probably true for ac3d models and most uses of material animations,
+// but will break down if, for example, you animate the transparency
+// of a vertex colored model.
+class MaterialDefaultsVisitor : public osg::NodeVisitor {
+public:
+ MaterialDefaultsVisitor()
+ : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
+ ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
{
- if (alpha != _alpha)
- _alpha = alpha;
- _updateMask |= TRANSPARENCY;
+ setVisitorType(osg::NodeVisitor::NODE_VISITOR);
}
- virtual void reset()
- {
- _updateMask = 0;
- }
virtual void apply(osg::Node& node)
{
- updateStateSet(node.getStateSet());
+ maybeGetMaterialValues(node.getStateSet());
traverse(node);
}
+
virtual void apply(osg::Geode& node)
{
- apply((osg::Node&)node);
- unsigned nDrawables = node.getNumDrawables();
- for (unsigned i = 0; i < nDrawables; ++i) {
- osg::Drawable* drawable = node.getDrawable(i);
- updateStateSet(drawable->getStateSet());
-
- if (_updateMask&TRANSPARENCY) {
- osg::Geometry* geometry = drawable->asGeometry();
- if (!geometry)
- continue;
- osg::Array* array = geometry->getColorArray();
- if (!array)
- continue;
- osg::Vec4Array* vec4Array = dynamic_cast<osg::Vec4Array*>(array);
- if (!vec4Array)
- continue;
-
- // FIXME, according to the colormode in the material
- // we might incorporate the apropriate color value
- geometry->dirtyDisplayList();
- vec4Array->dirty();
- for (unsigned k = 0; k < vec4Array->size(); ++k) {
- (*vec4Array)[k][3] = _alpha;
- }
+ maybeGetMaterialValues(node.getStateSet());
+ int numDrawables = node.getNumDrawables();
+ for (int i = 0; i < numDrawables; i++) {
+ osg::Geometry* geom = dynamic_cast<osg::Geometry*>(node.getDrawable(i));
+ if (!geom || geom->getColorBinding() != osg::Geometry::BIND_OVERALL)
+ continue;
+ maybeGetMaterialValues(geom->getStateSet());
+ osg::Array* colorArray = geom->getColorArray();
+ osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
+ if (colorVec4) {
+ ambientDiffuse = (*colorVec4)[0];
+ break;
+ }
+ osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
+ if (colorVec3) {
+ ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
+ break;
}
}
}
- void updateStateSet(osg::StateSet* stateSet)
+
+ void maybeGetMaterialValues(osg::StateSet* stateSet)
{
if (!stateSet)
return;
- osg::StateAttribute* stateAttribute;
- stateAttribute = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
- if (!stateAttribute)
+ osg::Material* nodeMat
+ = dynamic_cast<osg::Material*>(stateSet->getAttribute(osg::StateAttribute::MATERIAL));
+ if (!nodeMat)
return;
- osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
- if (!material)
- return;
- if (_updateMask&AMBIENT)
- material->setAmbient(osg::Material::FRONT_AND_BACK, _ambient.osg());
- if (_updateMask&DIFFUSE)
- material->setDiffuse(osg::Material::FRONT_AND_BACK, _diffuse.osg());
- if (_updateMask&SPECULAR)
- material->setSpecular(osg::Material::FRONT_AND_BACK, _specular.osg());
- if (_updateMask&EMISSION)
- material->setEmission(osg::Material::FRONT_AND_BACK, _emission.osg());
- if (_updateMask&SHININESS)
- material->setShininess(osg::Material::FRONT_AND_BACK, _shininess);
- if (_updateMask&TRANSPARENCY) {
- material->setAlpha(osg::Material::FRONT_AND_BACK, _alpha);
- if (_alpha < 1) {
- stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
- stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
- } else {
- stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
- }
- }
+ material = nodeMat;
}
-private:
- unsigned _updateMask;
- SGVec4f _ambient;
- SGVec4f _diffuse;
- SGVec4f _specular;
- SGVec4f _emission;
- float _shininess;
- float _alpha;
+
+ osg::ref_ptr<osg::Material> material;
+ osg::Vec4 ambientDiffuse;
+};
+
+class MaterialPropertyAdapter
+{
+public:
+ MaterialPropertyAdapter(const SGPropertyNode* configNode,
+ SGPropertyNode* modelRoot) :
+ _ambient(configNode->getChild("ambient"), modelRoot),
+ _diffuse(configNode->getChild("diffuse"), modelRoot),
+ _specular(configNode->getChild("specular"), modelRoot),
+ _emission(configNode->getChild("emission"), modelRoot),
+ _shininess("shininess", "shininess-prop",
+ configNode/*->getChild("shininess")*/, modelRoot),
+ _transparency("alpha", "alpha-prop",
+ configNode->getChild("transparency"), modelRoot)
+ {
+ _shininess.max = 128;
+ _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
+ || _emission.live() || _shininess.live()
+ || _transparency.live());
+ }
+ bool isAnimated() { return _isAnimated; }
+ // This takes a StateSet argument because the rendering bin will
+ // be changed if there is transparency.
+ void setMaterialValues(osg::StateSet* stateSet)
+ {
+ osg::StateAttribute* stateAttribute
+ = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
+ osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
+ if (material) {
+ if (_ambient.live() || _ambient.dirty())
+ material->setAmbient(osg::Material::FRONT_AND_BACK,
+ _ambient.rgbaVec4());
+ if (_diffuse.live() || _diffuse.dirty())
+ material->setDiffuse(osg::Material::FRONT_AND_BACK,
+ _diffuse.rgbaVec4());
+ if (_specular.live() || _specular.dirty())
+ material->setSpecular(osg::Material::FRONT_AND_BACK,
+ _specular.rgbaVec4());
+ if (_emission.live() || _emission.dirty())
+ material->setEmission(osg::Material::FRONT_AND_BACK,
+ _emission.rgbaVec4());
+ if (_shininess.live() || _shininess.dirty())
+ material->setShininess(osg::Material::FRONT_AND_BACK,
+ _shininess.getValue());
+ if (_transparency.live() || _transparency.dirty()) {
+ float alpha = _transparency.getValue();
+ material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
+ if (alpha < 1.0f) {
+ stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
+ stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
+ } else {
+ stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
+ }
+ }
+ }
+ }
+ ColorSpec _ambient;
+ ColorSpec _diffuse;
+ ColorSpec _specular;
+ ColorSpec _emission;
+ PropSpec _shininess;
+ PropSpec _transparency;
+ bool _isAnimated;
+
};
-class SGMaterialAnimation::UpdateCallback : public osg::NodeCallback {
+class UpdateCallback : public osg::NodeCallback {
public:
UpdateCallback(const osgDB::FilePathList& texturePathList,
const SGCondition* condition,
const SGPropertyNode* configNode, SGPropertyNode* modelRoot) :
_condition(condition),
- _ambient(configNode->getChild("ambient"), modelRoot),
- _diffuse(configNode->getChild("diffuse"), modelRoot),
- _specular(configNode->getChild("specular"), modelRoot),
- _emission(configNode->getChild("emission"), modelRoot),
- _shininess("shininess", "shininess-prop",
- configNode->getChild("shininess"), modelRoot),
- _transparency("alpha", "alpha-prop",
- configNode->getChild("transparency"), modelRoot),
- _texturePathList(texturePathList)
+ _materialProps(configNode, modelRoot),
+ _texturePathList(texturePathList),
+ _prevState(false)
{
const SGPropertyNode* node;
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
- if (!_condition || _condition->test()) {
+ osg::StateSet* stateSet = node->getStateSet();
+ if ((!_condition || _condition->test()) && stateSet) {
if (_textureProp) {
std::string textureName = _textureProp->getStringValue();
if (_textureName != textureName) {
- osg::StateSet* stateSet = node->getOrCreateStateSet();
- while (stateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE)) {
+ while (stateSet->getTextureAttribute(0,
+ osg::StateAttribute::TEXTURE)) {
stateSet->removeTextureAttribute(0, osg::StateAttribute::TEXTURE);
}
std::string textureFile;
if (!textureFile.empty()) {
osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
if (texture2D) {
- stateSet->setTextureAttribute(0, texture2D);
+ stateSet->setTextureAttribute(0, texture2D,
+ osg::StateAttribute::OVERRIDE);
stateSet->setTextureMode(0, GL_TEXTURE_2D,
osg::StateAttribute::ON);
_textureName = textureName;
osg::StateSet* stateSet = node->getOrCreateStateSet();
osg::StateAttribute* stateAttribute;
stateAttribute = stateSet->getAttribute(osg::StateAttribute::ALPHAFUNC);
- assert(dynamic_cast<osg::AlphaFunc*>(stateAttribute));
- osg::AlphaFunc* alphaFunc = static_cast<osg::AlphaFunc*>(stateAttribute);
+ osg::AlphaFunc* alphaFunc
+ = dynamic_cast<osg::AlphaFunc*>(stateAttribute);
+ assert(alphaFunc);
alphaFunc->setReferenceValue(_thresholdProp->getFloatValue());
}
-
- _visitor.reset();
- if (_ambient.live())
- _visitor.setAmbient(_ambient.rgba());
- if (_diffuse.live())
- _visitor.setDiffuse(_diffuse.rgba());
- if (_specular.live())
- _visitor.setSpecular(_specular.rgba());
- if (_emission.live())
- _visitor.setEmission(_emission.rgba());
- if (_shininess.live())
- _visitor.setShininess(_shininess.getValue());
- if (_transparency.live())
- _visitor.setAlpha(_transparency.getValue());
-
- node->accept(_visitor);
+ if (_materialProps.isAnimated() || !_prevState)
+ _materialProps.setMaterialValues(stateSet);
+ _prevState = true;
+ } else {
+ _prevState = false;
}
-
traverse(node, nv);
}
private:
SGSharedPtr<const SGCondition> _condition;
SGSharedPtr<const SGPropertyNode> _textureProp;
SGSharedPtr<const SGPropertyNode> _thresholdProp;
- MaterialVisitor _visitor;
std::string _textureName;
- ColorSpec _ambient;
- ColorSpec _diffuse;
- ColorSpec _specular;
- ColorSpec _emission;
- PropSpec _shininess;
- PropSpec _transparency;
+ MaterialPropertyAdapter _materialProps;
osgDB::FilePathList _texturePathList;
+ bool _prevState;
};
+} // namespace
+
SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
- SGPropertyNode* modelRoot) :
- SGAnimation(configNode, modelRoot)
+ SGPropertyNode* modelRoot,
+ const osgDB::ReaderWriter::Options*
+ options) :
+ SGAnimation(configNode, modelRoot),
+ texturePathList(options->getDatabasePathList())
{
if (configNode->hasChild("global"))
- SG_LOG(SG_IO, SG_ALERT, "Using global material animation that can "
- "no longer work");
+ SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
+ "no longer supported");
}
osg::Group*
SGPropertyNode* inputRoot = getModelRoot();
const SGPropertyNode* node = getConfig()->getChild("property-base");
if (node)
- inputRoot = getModelRoot()->getRootNode()->getNode(node->getStringValue(),
- true);
-
- osgDB::FilePathList texturePathList = osgDB::getDataFilePathList();
-
+ inputRoot = getModelRoot()->getNode(node->getStringValue(), true);
+ osg::StateSet* stateSet = group->getOrCreateStateSet();
if (getConfig()->hasChild("texture")) {
std::string textureName = getConfig()->getStringValue("texture");
std::string textureFile;
textureFile = osgDB::findFileInPath(textureName, texturePathList);
if (!textureFile.empty()) {
- osg::StateSet* stateSet = group->getOrCreateStateSet();
osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
if (texture2D) {
- stateSet->setTextureAttribute(0, texture2D);
+ stateSet->setTextureAttribute(0, texture2D,
+ osg::StateAttribute::OVERRIDE);
stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
if (texture2D->getImage()->isImageTranslucent()) {
stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
if (getConfig()->hasChild("threshold-prop") ||
getConfig()->hasChild("threshold")) {
- osg::StateSet* stateSet = group->getOrCreateStateSet();
osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
alphaFunc->setFunction(osg::AlphaFunc::GREATER);
float threshold = getConfig()->getFloatValue("threshold", 0);
alphaFunc->setReferenceValue(threshold);
- stateSet->setAttributeAndModes(alphaFunc);
+ stateSet->setAttribute(alphaFunc, osg::StateAttribute::OVERRIDE);
}
- UpdateCallback* updateCallback;
- updateCallback = new UpdateCallback(texturePathList, getCondition(),
- getConfig(), inputRoot);
- group->setUpdateCallback(updateCallback);
+ unsigned suppliedColors = 0;
+ if (getConfig()->hasChild("ambient"))
+ suppliedColors |= AMBIENT;
+ if (getConfig()->hasChild("diffuse"))
+ suppliedColors |= DIFFUSE;
+ if (getConfig()->hasChild("specular"))
+ suppliedColors |= SPECULAR;
+ if (getConfig()->hasChild("emission"))
+ suppliedColors |= EMISSION;
+ if (getConfig()->hasChild("shininess")
+ || getConfig()->hasChild("shininess-prop"))
+ suppliedColors |= SHININESS;
+ if (getConfig()->hasChild("transparency"))
+ suppliedColors |= TRANSPARENCY;
+ osg::Material* mat = 0;
+ if (suppliedColors != 0) {
+ if (defaultMaterial.valid()) {
+ mat = defaultMaterial.get();
+
+ } else {
+ mat = new osg::Material;
+ mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
+ }
+ mat->setDataVariance(osg::Object::DYNAMIC);
+ unsigned defaultColorModeMask = 0;
+ mat->setUpdateCallback(0); // Just to make sure.
+ switch (mat->getColorMode()) {
+ case osg::Material::OFF:
+ defaultColorModeMask = 0;
+ break;
+ case osg::Material::AMBIENT:
+ defaultColorModeMask = AMBIENT;
+ break;
+ case osg::Material::DIFFUSE:
+ defaultColorModeMask = DIFFUSE;
+ break;
+ case osg::Material::AMBIENT_AND_DIFFUSE:
+ defaultColorModeMask = AMBIENT | DIFFUSE;
+ break;
+ case osg::Material::SPECULAR:
+ defaultColorModeMask = SPECULAR;
+ break;
+ case osg::Material::EMISSION:
+ defaultColorModeMask = EMISSION;
+ break;
+ }
+ // Copy the color found by traversing geometry into the material
+ // in case we need to specify it (e.g., transparency) and it is
+ // not specified by the animation.
+ if (defaultAmbientDiffuse.x() >= 0) {
+ if (defaultColorModeMask & AMBIENT)
+ mat->setAmbient(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
+ if (defaultColorModeMask & DIFFUSE)
+ mat->setDiffuse(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
+ }
+ // Compute which colors in the animation override colors set via
+ // colorMode / glColor, and set the colorMode for the animation's
+ // material accordingly.
+ if (suppliedColors & TRANSPARENCY) {
+ // All colors will be affected by the material. Hope all the
+ // defaults are fine, if needed.
+ mat->setColorMode(osg::Material::OFF);
+ } else if ((suppliedColors & defaultColorModeMask) != 0) {
+ // First deal with the complicated AMBIENT/DIFFUSE case.
+ if ((defaultColorModeMask & AMBIENT_DIFFUSE) != 0) {
+ // glColor can supply colors not specified by the animation.
+ unsigned matColorModeMask = ((~suppliedColors & defaultColorModeMask)
+ & AMBIENT_DIFFUSE);
+ if ((matColorModeMask & DIFFUSE) != 0)
+ mat->setColorMode(osg::Material::DIFFUSE);
+ else if ((matColorModeMask & AMBIENT) != 0)
+ mat->setColorMode(osg::Material::AMBIENT);
+ else
+ mat->setColorMode(osg::Material::OFF);
+ } else {
+ // The animation overrides the glColor color.
+ mat->setColorMode(osg::Material::OFF);
+ }
+ } else {
+ // No overlap between the animation and color mode, so leave
+ // the color mode alone.
+ }
+ stateSet->setAttribute(mat,(osg::StateAttribute::ON
+ | osg::StateAttribute::OVERRIDE));
+ }
+ bool matAnimated = false;
+ if (mat) {
+ MaterialPropertyAdapter adapter(getConfig(), inputRoot);
+ adapter.setMaterialValues(stateSet);
+ matAnimated = adapter.isAnimated();
+ }
+ if (matAnimated || getConfig()->hasChild("texture-prop")
+ || getConfig()->hasChild("threshold-prop") || getCondition()) {
+ stateSet->setDataVariance(osg::Object::DYNAMIC);
+ group->setUpdateCallback(new UpdateCallback(texturePathList,
+ getCondition(),
+ getConfig(), inputRoot));
+ } else {
+ stateSet->setDataVariance(osg::Object::STATIC);
+ }
parent.addChild(group);
return group;
}
SGMaterialAnimation::install(osg::Node& node)
{
SGAnimation::install(node);
- // make sure everything (except the texture attributes)
- // below is private to our model
- cloneDrawables(node);
-
- // Remove all textures if required, they get replaced later on
- if (getConfig()->hasChild("texture") ||
- getConfig()->hasChild("texture-prop")) {
- removeTextureAttribute(node, 0, osg::StateAttribute::TEXTURE);
- removeTextureMode(node, 0, GL_TEXTURE_2D);
- }
- // Remove all nested alphaFuncs
- if (getConfig()->hasChild("threshold") ||
- getConfig()->hasChild("threshold-prop"))
- removeAttribute(node, osg::StateAttribute::ALPHAFUNC);
-
- ColorSpec ambient(getConfig()->getChild("ambient"), getModelRoot());
- ColorSpec diffuse(getConfig()->getChild("diffuse"), getModelRoot());
- ColorSpec specular(getConfig()->getChild("specular"), getModelRoot());
- ColorSpec emission(getConfig()->getChild("emission"), getModelRoot());
- PropSpec shininess("shininess", "shininess-prop",
- getConfig()->getChild("shininess"), getModelRoot());
- PropSpec transparency("alpha", "alpha-prop",
- getConfig()->getChild("transparency"), getModelRoot());
- MaterialVisitor visitor;
- if (ambient.dirty())
- visitor.setAmbient(ambient.initialRgba());
- if (diffuse.dirty())
- visitor.setDiffuse(diffuse.initialRgba());
- if (specular.dirty())
- visitor.setSpecular(specular.initialRgba());
- if (emission.dirty())
- visitor.setEmission(emission.initialRgba());
- if (shininess.dirty())
- visitor.setShininess(shininess.getInitialValue());
- if (transparency.dirty())
- visitor.setAlpha(transparency.getInitialValue());
- node.accept(visitor);
+ MaterialDefaultsVisitor defaultsVisitor;
+ node.accept(defaultsVisitor);
+ if (defaultsVisitor.material.valid()) {
+ defaultMaterial
+ = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
+ }
+ defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;
}
-