1 // animation.cxx - classes to manage model animation.
2 // Written by David Megginson, started 2002.
4 // This file is in the Public Domain, and comes with no warranty.
7 # include <simgear_config.h>
10 #include "SGMaterialAnimation.hxx"
12 #include <osg/AlphaFunc>
14 #include <osg/Drawable>
16 #include <osg/Geometry>
17 #include <osg/Material>
18 #include <osg/StateSet>
19 #include <osgDB/FileNameUtils>
20 #include <osgDB/FileUtils>
21 #include <osgDB/ReadFile>
23 #include <simgear/props/condition.hxx>
24 #include <simgear/props/props.hxx>
25 #include <simgear/scene/material/Effect.hxx>
26 #include <simgear/scene/material/EffectGeode.hxx>
27 #include <simgear/scene/material/Pass.hxx>
28 #include <simgear/scene/material/Technique.hxx>
29 #include <simgear/scene/model/model.hxx>
35 * Get a color from properties.
38 float red, green, blue;
41 SGPropertyNode_ptr red_prop;
42 SGPropertyNode_ptr green_prop;
43 SGPropertyNode_ptr blue_prop;
44 SGPropertyNode_ptr factor_prop;
45 SGPropertyNode_ptr offset_prop;
48 ColorSpec(const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
56 red = configNode->getFloatValue("red", -1.0);
57 green = configNode->getFloatValue("green", -1.0);
58 blue = configNode->getFloatValue("blue", -1.0);
59 factor = configNode->getFloatValue("factor", 1.0);
60 offset = configNode->getFloatValue("offset", 0.0);
64 const SGPropertyNode *node;
65 node = configNode->getChild("red-prop");
67 red_prop = modelRoot->getNode(node->getStringValue(), true);
68 node = configNode->getChild("green-prop");
70 green_prop = modelRoot->getNode(node->getStringValue(), true);
71 node = configNode->getChild("blue-prop");
73 blue_prop = modelRoot->getNode(node->getStringValue(), true);
74 node = configNode->getChild("factor-prop");
76 factor_prop = modelRoot->getNode(node->getStringValue(), true);
77 node = configNode->getChild("offset-prop");
79 offset_prop = modelRoot->getNode(node->getStringValue(), true);
83 return red >= 0 || green >= 0 || blue >= 0;
86 return red_prop || green_prop || blue_prop
87 || factor_prop || offset_prop;
91 red = red_prop->getFloatValue();
93 green = green_prop->getFloatValue();
95 blue = blue_prop->getFloatValue();
97 factor = factor_prop->getFloatValue();
99 offset = offset_prop->getFloatValue();
100 v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
101 v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
102 v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
109 return toOsg(rgba());
112 SGVec4f &initialRgba() {
113 v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
114 v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
115 v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
122 * Get a property value from a property.
130 SGPropertyNode_ptr value_prop;
131 SGPropertyNode_ptr factor_prop;
132 SGPropertyNode_ptr offset_prop;
134 PropSpec(const char* valueName, const char* valuePropName,
135 const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
141 value = configNode->getFloatValue(valueName, -1);
142 factor = configNode->getFloatValue("factor", 1);
143 offset = configNode->getFloatValue("offset", 0);
144 min = configNode->getFloatValue("min", 0);
145 max = configNode->getFloatValue("max", 1);
149 const SGPropertyNode *node;
150 node = configNode->getChild(valuePropName);
152 value_prop = modelRoot->getNode(node->getStringValue(), true);
153 node = configNode->getChild("factor-prop");
155 factor_prop = modelRoot->getNode(node->getStringValue(), true);
156 node = configNode->getChild("offset-prop");
158 offset_prop = modelRoot->getNode(node->getStringValue(), true);
160 bool dirty() { return value >= 0.0; }
161 bool live() { return value_prop || factor_prop || offset_prop; }
165 value = value_prop->getFloatValue();
167 offset = offset_prop->getFloatValue();
169 factor = factor_prop->getFloatValue();
170 return SGMiscf::clip(value*factor + offset, min, max);
172 float getInitialValue()
174 return SGMiscf::clip(value*factor + offset, min, max);
179 * The possible color properties supplied by a material animation.
190 const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
192 const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
195 // Visitor for finding default material colors in the animation node's
196 // subgraph. This makes some assumptions about the subgraph i.e.,
197 // there will be one material and one color value found. This is
198 // probably true for ac3d models and most uses of material animations,
199 // but will break down if, for example, you animate the transparency
200 // of a vertex colored model.
201 class MaterialDefaultsVisitor : public osg::NodeVisitor {
203 MaterialDefaultsVisitor()
204 : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
205 ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
207 setVisitorType(osg::NodeVisitor::NODE_VISITOR);
210 virtual void apply(osg::Node& node)
212 maybeGetMaterialValues(node.getStateSet());
216 virtual void apply(osg::Geode& node)
218 using namespace simgear;
219 EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
221 const Effect* effect = eg->getEffect();
223 for (vector<osg::ref_ptr<Technique> >::const_iterator itr
224 = effect->techniques.begin(), end = effect->techniques.end();
227 const Technique* tniq = itr->get();
228 for (vector<osg::ref_ptr<Pass> >::const_iterator pitr
229 = tniq->passes.begin(), pend = tniq->passes.end();
232 maybeGetMaterialValues(pitr->get());
235 maybeGetMaterialValues(node.getStateSet());
237 int numDrawables = node.getNumDrawables();
238 for (int i = 0; i < numDrawables; i++) {
239 osg::Geometry* geom = dynamic_cast<osg::Geometry*>(node.getDrawable(i));
240 if (!geom || geom->getColorBinding() != osg::Geometry::BIND_OVERALL)
242 maybeGetMaterialValues(geom->getStateSet());
243 osg::Array* colorArray = geom->getColorArray();
244 osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
246 ambientDiffuse = (*colorVec4)[0];
249 osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
251 ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
257 void maybeGetMaterialValues(const osg::StateSet* stateSet)
261 const osg::Material* nodeMat
262 = dynamic_cast<const osg::Material*>(stateSet
263 ->getAttribute(osg::StateAttribute
270 osg::ref_ptr<const osg::Material> material;
271 osg::Vec4 ambientDiffuse;
274 class MaterialPropertyAdapter
277 MaterialPropertyAdapter(const SGPropertyNode* configNode,
278 SGPropertyNode* modelRoot) :
279 _ambient(configNode->getChild("ambient"), modelRoot),
280 _diffuse(configNode->getChild("diffuse"), modelRoot),
281 _specular(configNode->getChild("specular"), modelRoot),
282 _emission(configNode->getChild("emission"), modelRoot),
283 _shininess("shininess", "shininess-prop",
284 configNode/*->getChild("shininess")*/, modelRoot),
285 _transparency("alpha", "alpha-prop",
286 configNode->getChild("transparency"), modelRoot)
288 _shininess.max = 128;
289 _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
290 || _emission.live() || _shininess.live()
291 || _transparency.live());
293 bool isAnimated() { return _isAnimated; }
294 // This takes a StateSet argument because the rendering bin will
295 // be changed if there is transparency.
296 void setMaterialValues(osg::StateSet* stateSet)
298 osg::StateAttribute* stateAttribute
299 = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
300 osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
302 if (_ambient.live() || _ambient.dirty())
303 material->setAmbient(osg::Material::FRONT_AND_BACK,
304 _ambient.rgbaVec4());
305 if (_diffuse.live() || _diffuse.dirty())
306 material->setDiffuse(osg::Material::FRONT_AND_BACK,
307 _diffuse.rgbaVec4());
308 if (_specular.live() || _specular.dirty())
309 material->setSpecular(osg::Material::FRONT_AND_BACK,
310 _specular.rgbaVec4());
311 if (_emission.live() || _emission.dirty())
312 material->setEmission(osg::Material::FRONT_AND_BACK,
313 _emission.rgbaVec4());
314 if (_shininess.live() || _shininess.dirty())
315 material->setShininess(osg::Material::FRONT_AND_BACK,
316 _shininess.getValue());
317 if (_transparency.live() || _transparency.dirty()) {
318 float alpha = _transparency.getValue();
319 material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
321 stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
322 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
324 stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
334 PropSpec _transparency;
339 class UpdateCallback : public osg::NodeCallback {
341 UpdateCallback(const osgDB::FilePathList& texturePathList,
342 const SGCondition* condition,
343 const SGPropertyNode* configNode, SGPropertyNode* modelRoot) :
344 _condition(condition),
345 _materialProps(configNode, modelRoot),
346 _texturePathList(texturePathList),
349 const SGPropertyNode* node;
351 node = configNode->getChild("threshold-prop");
353 _thresholdProp = modelRoot->getNode(node->getStringValue(), true);
354 node = configNode->getChild("texture-prop");
356 _textureProp = modelRoot->getNode(node->getStringValue(), true);
359 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
361 osg::StateSet* stateSet = node->getStateSet();
362 if ((!_condition || _condition->test()) && stateSet) {
364 std::string textureName = _textureProp->getStringValue();
365 if (_textureName != textureName) {
366 while (stateSet->getTextureAttribute(0,
367 osg::StateAttribute::TEXTURE)) {
368 stateSet->removeTextureAttribute(0, osg::StateAttribute::TEXTURE);
370 std::string textureFile;
371 textureFile = osgDB::findFileInPath(textureName, _texturePathList);
372 if (!textureFile.empty()) {
373 osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
375 stateSet->setTextureAttribute(0, texture2D,
376 osg::StateAttribute::OVERRIDE);
377 stateSet->setTextureMode(0, GL_TEXTURE_2D,
378 osg::StateAttribute::ON);
379 _textureName = textureName;
384 if (_thresholdProp) {
385 osg::StateSet* stateSet = node->getOrCreateStateSet();
386 osg::StateAttribute* stateAttribute;
387 stateAttribute = stateSet->getAttribute(osg::StateAttribute::ALPHAFUNC);
388 osg::AlphaFunc* alphaFunc
389 = dynamic_cast<osg::AlphaFunc*>(stateAttribute);
391 alphaFunc->setReferenceValue(_thresholdProp->getFloatValue());
393 if (_materialProps.isAnimated() || !_prevState)
394 _materialProps.setMaterialValues(stateSet);
402 SGSharedPtr<const SGCondition> _condition;
403 SGSharedPtr<const SGPropertyNode> _textureProp;
404 SGSharedPtr<const SGPropertyNode> _thresholdProp;
405 std::string _textureName;
406 MaterialPropertyAdapter _materialProps;
407 osgDB::FilePathList _texturePathList;
413 SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
414 SGPropertyNode* modelRoot,
415 const osgDB::ReaderWriter::Options*
417 SGAnimation(configNode, modelRoot),
418 texturePathList(options->getDatabasePathList())
420 if (configNode->hasChild("global"))
421 SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
422 "no longer supported");
426 SGMaterialAnimation::createAnimationGroup(osg::Group& parent)
428 osg::Group* group = new osg::Group;
429 group->setName("material animation group");
431 SGPropertyNode* inputRoot = getModelRoot();
432 const SGPropertyNode* node = getConfig()->getChild("property-base");
434 inputRoot = getModelRoot()->getNode(node->getStringValue(), true);
435 osg::StateSet* stateSet = group->getOrCreateStateSet();
436 if (getConfig()->hasChild("texture")) {
437 std::string textureName = getConfig()->getStringValue("texture");
438 std::string textureFile;
439 textureFile = osgDB::findFileInPath(textureName, texturePathList);
440 if (!textureFile.empty()) {
441 osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
443 stateSet->setTextureAttribute(0, texture2D,
444 osg::StateAttribute::OVERRIDE);
445 stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
446 if (texture2D->getImage()->isImageTranslucent()) {
447 stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
448 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
453 if (getConfig()->hasChild("threshold-prop") ||
454 getConfig()->hasChild("threshold")) {
455 osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
456 alphaFunc->setFunction(osg::AlphaFunc::GREATER);
457 float threshold = getConfig()->getFloatValue("threshold", 0);
458 alphaFunc->setReferenceValue(threshold);
459 stateSet->setAttribute(alphaFunc, osg::StateAttribute::OVERRIDE);
462 unsigned suppliedColors = 0;
463 if (getConfig()->hasChild("ambient"))
464 suppliedColors |= AMBIENT;
465 if (getConfig()->hasChild("diffuse"))
466 suppliedColors |= DIFFUSE;
467 if (getConfig()->hasChild("specular"))
468 suppliedColors |= SPECULAR;
469 if (getConfig()->hasChild("emission"))
470 suppliedColors |= EMISSION;
471 if (getConfig()->hasChild("shininess")
472 || getConfig()->hasChild("shininess-prop"))
473 suppliedColors |= SHININESS;
474 if (getConfig()->hasChild("transparency"))
475 suppliedColors |= TRANSPARENCY;
476 osg::Material* mat = 0;
477 if (suppliedColors != 0) {
478 if (defaultMaterial.valid()) {
479 mat = defaultMaterial.get();
482 mat = new osg::Material;
483 mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
485 mat->setDataVariance(osg::Object::DYNAMIC);
486 unsigned defaultColorModeMask = 0;
487 mat->setUpdateCallback(0); // Just to make sure.
488 switch (mat->getColorMode()) {
489 case osg::Material::OFF:
490 defaultColorModeMask = 0;
492 case osg::Material::AMBIENT:
493 defaultColorModeMask = AMBIENT;
495 case osg::Material::DIFFUSE:
496 defaultColorModeMask = DIFFUSE;
498 case osg::Material::AMBIENT_AND_DIFFUSE:
499 defaultColorModeMask = AMBIENT | DIFFUSE;
501 case osg::Material::SPECULAR:
502 defaultColorModeMask = SPECULAR;
504 case osg::Material::EMISSION:
505 defaultColorModeMask = EMISSION;
508 // Copy the color found by traversing geometry into the material
509 // in case we need to specify it (e.g., transparency) and it is
510 // not specified by the animation.
511 if (defaultAmbientDiffuse.x() >= 0) {
512 if (defaultColorModeMask & AMBIENT)
513 mat->setAmbient(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
514 if (defaultColorModeMask & DIFFUSE)
515 mat->setDiffuse(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
517 // Compute which colors in the animation override colors set via
518 // colorMode / glColor, and set the colorMode for the animation's
519 // material accordingly.
520 if (suppliedColors & TRANSPARENCY) {
521 // All colors will be affected by the material. Hope all the
522 // defaults are fine, if needed.
523 mat->setColorMode(osg::Material::OFF);
524 } else if ((suppliedColors & defaultColorModeMask) != 0) {
525 // First deal with the complicated AMBIENT/DIFFUSE case.
526 if ((defaultColorModeMask & AMBIENT_DIFFUSE) != 0) {
527 // glColor can supply colors not specified by the animation.
528 unsigned matColorModeMask = ((~suppliedColors & defaultColorModeMask)
530 if ((matColorModeMask & DIFFUSE) != 0)
531 mat->setColorMode(osg::Material::DIFFUSE);
532 else if ((matColorModeMask & AMBIENT) != 0)
533 mat->setColorMode(osg::Material::AMBIENT);
535 mat->setColorMode(osg::Material::OFF);
537 // The animation overrides the glColor color.
538 mat->setColorMode(osg::Material::OFF);
541 // No overlap between the animation and color mode, so leave
542 // the color mode alone.
544 stateSet->setAttribute(mat,(osg::StateAttribute::ON
545 | osg::StateAttribute::OVERRIDE));
547 bool matAnimated = false;
549 MaterialPropertyAdapter adapter(getConfig(), inputRoot);
550 adapter.setMaterialValues(stateSet);
551 matAnimated = adapter.isAnimated();
553 if (matAnimated || getConfig()->hasChild("texture-prop")
554 || getConfig()->hasChild("threshold-prop") || getCondition()) {
555 stateSet->setDataVariance(osg::Object::DYNAMIC);
556 group->setUpdateCallback(new UpdateCallback(texturePathList,
558 getConfig(), inputRoot));
560 stateSet->setDataVariance(osg::Object::STATIC);
562 parent.addChild(group);
567 SGMaterialAnimation::install(osg::Node& node)
569 SGAnimation::install(node);
571 MaterialDefaultsVisitor defaultsVisitor;
572 node.accept(defaultsVisitor);
573 if (defaultsVisitor.material.valid()) {
575 = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
577 defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;