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>
32 using namespace simgear;
36 * Get a color from properties.
39 float red, green, blue;
42 SGPropertyNode_ptr red_prop;
43 SGPropertyNode_ptr green_prop;
44 SGPropertyNode_ptr blue_prop;
45 SGPropertyNode_ptr factor_prop;
46 SGPropertyNode_ptr offset_prop;
49 ColorSpec(const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
57 red = configNode->getFloatValue("red", -1.0);
58 green = configNode->getFloatValue("green", -1.0);
59 blue = configNode->getFloatValue("blue", -1.0);
60 factor = configNode->getFloatValue("factor", 1.0);
61 offset = configNode->getFloatValue("offset", 0.0);
65 const SGPropertyNode *node;
66 node = configNode->getChild("red-prop");
68 red_prop = modelRoot->getNode(node->getStringValue(), true);
69 node = configNode->getChild("green-prop");
71 green_prop = modelRoot->getNode(node->getStringValue(), true);
72 node = configNode->getChild("blue-prop");
74 blue_prop = modelRoot->getNode(node->getStringValue(), true);
75 node = configNode->getChild("factor-prop");
77 factor_prop = modelRoot->getNode(node->getStringValue(), true);
78 node = configNode->getChild("offset-prop");
80 offset_prop = modelRoot->getNode(node->getStringValue(), true);
84 return red >= 0 || green >= 0 || blue >= 0;
87 return red_prop || green_prop || blue_prop
88 || factor_prop || offset_prop;
92 red = red_prop->getFloatValue();
94 green = green_prop->getFloatValue();
96 blue = blue_prop->getFloatValue();
98 factor = factor_prop->getFloatValue();
100 offset = offset_prop->getFloatValue();
101 v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
102 v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
103 v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
110 return toOsg(rgba());
113 SGVec4f &initialRgba() {
114 v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
115 v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
116 v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
123 * Get a property value from a property.
131 SGPropertyNode_ptr value_prop;
132 SGPropertyNode_ptr factor_prop;
133 SGPropertyNode_ptr offset_prop;
135 PropSpec(const char* valueName, const char* valuePropName,
136 const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
142 value = configNode->getFloatValue(valueName, -1);
143 factor = configNode->getFloatValue("factor", 1);
144 offset = configNode->getFloatValue("offset", 0);
145 min = configNode->getFloatValue("min", 0);
146 max = configNode->getFloatValue("max", 1);
150 const SGPropertyNode *node;
151 node = configNode->getChild(valuePropName);
153 value_prop = modelRoot->getNode(node->getStringValue(), true);
154 node = configNode->getChild("factor-prop");
156 factor_prop = modelRoot->getNode(node->getStringValue(), true);
157 node = configNode->getChild("offset-prop");
159 offset_prop = modelRoot->getNode(node->getStringValue(), true);
161 bool dirty() { return value >= 0.0; }
162 bool live() { return value_prop || factor_prop || offset_prop; }
166 value = value_prop->getFloatValue();
168 offset = offset_prop->getFloatValue();
170 factor = factor_prop->getFloatValue();
171 return SGMiscf::clip(value*factor + offset, min, max);
173 float getInitialValue()
175 return SGMiscf::clip(value*factor + offset, min, max);
180 * The possible color properties supplied by a material animation.
191 const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
193 const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
196 // Visitor for finding default material colors in the animation node's
197 // subgraph. This makes some assumptions about the subgraph i.e.,
198 // there will be one material and one color value found. This is
199 // probably true for ac3d models and most uses of material animations,
200 // but will break down if, for example, you animate the transparency
201 // of a vertex colored model.
202 class MaterialDefaultsVisitor : public osg::NodeVisitor {
204 MaterialDefaultsVisitor()
205 : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
206 ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
208 setVisitorType(osg::NodeVisitor::NODE_VISITOR);
211 virtual void apply(osg::Node& node)
213 maybeGetMaterialValues(node.getStateSet());
217 virtual void apply(osg::Geode& node)
219 using namespace simgear;
220 EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
222 const Effect* effect = eg->getEffect();
224 for (vector<osg::ref_ptr<Technique> >::const_iterator itr
225 = effect->techniques.begin(), end = effect->techniques.end();
228 const Technique* tniq = itr->get();
229 for (vector<osg::ref_ptr<Pass> >::const_iterator pitr
230 = tniq->passes.begin(), pend = tniq->passes.end();
233 maybeGetMaterialValues(pitr->get());
236 maybeGetMaterialValues(node.getStateSet());
238 int numDrawables = node.getNumDrawables();
239 for (int i = 0; i < numDrawables; i++) {
240 osg::Geometry* geom = dynamic_cast<osg::Geometry*>(node.getDrawable(i));
241 if (!geom || geom->getColorBinding() != osg::Geometry::BIND_OVERALL)
243 maybeGetMaterialValues(geom->getStateSet());
244 osg::Array* colorArray = geom->getColorArray();
245 osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
247 ambientDiffuse = (*colorVec4)[0];
250 osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
252 ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
258 void maybeGetMaterialValues(const osg::StateSet* stateSet)
262 const osg::Material* nodeMat
263 = dynamic_cast<const osg::Material*>(stateSet
264 ->getAttribute(osg::StateAttribute
271 osg::ref_ptr<const osg::Material> material;
272 osg::Vec4 ambientDiffuse;
275 class MaterialPropertyAdapter
278 MaterialPropertyAdapter(const SGPropertyNode* configNode,
279 SGPropertyNode* modelRoot) :
280 _ambient(configNode->getChild("ambient"), modelRoot),
281 _diffuse(configNode->getChild("diffuse"), modelRoot),
282 _specular(configNode->getChild("specular"), modelRoot),
283 _emission(configNode->getChild("emission"), modelRoot),
284 _shininess("shininess", "shininess-prop",
285 configNode/*->getChild("shininess")*/, modelRoot),
286 _transparency("alpha", "alpha-prop",
287 configNode->getChild("transparency"), modelRoot)
289 _shininess.max = 128;
290 _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
291 || _emission.live() || _shininess.live()
292 || _transparency.live());
294 bool isAnimated() { return _isAnimated; }
295 // This takes a StateSet argument because the rendering bin will
296 // be changed if there is transparency.
297 void setMaterialValues(osg::StateSet* stateSet)
299 osg::StateAttribute* stateAttribute
300 = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
301 osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
303 if (_ambient.live() || _ambient.dirty())
304 material->setAmbient(osg::Material::FRONT_AND_BACK,
305 _ambient.rgbaVec4());
306 if (_diffuse.live() || _diffuse.dirty())
307 material->setDiffuse(osg::Material::FRONT_AND_BACK,
308 _diffuse.rgbaVec4());
309 if (_specular.live() || _specular.dirty())
310 material->setSpecular(osg::Material::FRONT_AND_BACK,
311 _specular.rgbaVec4());
312 if (_emission.live() || _emission.dirty())
313 material->setEmission(osg::Material::FRONT_AND_BACK,
314 _emission.rgbaVec4());
315 if (_shininess.live() || _shininess.dirty())
316 material->setShininess(osg::Material::FRONT_AND_BACK,
317 _shininess.getValue());
318 if (_transparency.live() || _transparency.dirty()) {
319 float alpha = _transparency.getValue();
320 material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
322 stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
323 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
325 stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
335 PropSpec _transparency;
340 class UpdateCallback : public osg::NodeCallback {
342 UpdateCallback(const osgDB::FilePathList& texturePathList,
343 const SGCondition* condition,
344 const SGPropertyNode* configNode, SGPropertyNode* modelRoot) :
345 _condition(condition),
346 _materialProps(configNode, modelRoot),
347 _texturePathList(texturePathList),
350 const SGPropertyNode* node;
352 node = configNode->getChild("threshold-prop");
354 _thresholdProp = modelRoot->getNode(node->getStringValue(), true);
355 node = configNode->getChild("texture-prop");
357 _textureProp = modelRoot->getNode(node->getStringValue(), true);
360 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
362 osg::StateSet* stateSet = node->getStateSet();
363 if ((!_condition || _condition->test()) && stateSet) {
365 std::string textureName = _textureProp->getStringValue();
366 if (_textureName != textureName) {
367 while (stateSet->getTextureAttribute(0,
368 osg::StateAttribute::TEXTURE)) {
369 stateSet->removeTextureAttribute(0, osg::StateAttribute::TEXTURE);
371 std::string textureFile;
372 textureFile = osgDB::findFileInPath(textureName, _texturePathList);
373 if (!textureFile.empty()) {
374 osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
376 stateSet->setTextureAttribute(0, texture2D,
377 osg::StateAttribute::OVERRIDE);
378 stateSet->setTextureMode(0, GL_TEXTURE_2D,
379 osg::StateAttribute::ON);
380 _textureName = textureName;
385 if (_thresholdProp) {
386 osg::StateSet* stateSet = node->getOrCreateStateSet();
387 osg::StateAttribute* stateAttribute;
388 stateAttribute = stateSet->getAttribute(osg::StateAttribute::ALPHAFUNC);
389 osg::AlphaFunc* alphaFunc
390 = dynamic_cast<osg::AlphaFunc*>(stateAttribute);
392 alphaFunc->setReferenceValue(_thresholdProp->getFloatValue());
394 if (_materialProps.isAnimated() || !_prevState)
395 _materialProps.setMaterialValues(stateSet);
403 SGSharedPtr<const SGCondition> _condition;
404 SGSharedPtr<const SGPropertyNode> _textureProp;
405 SGSharedPtr<const SGPropertyNode> _thresholdProp;
406 std::string _textureName;
407 MaterialPropertyAdapter _materialProps;
408 osgDB::FilePathList _texturePathList;
414 SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
415 SGPropertyNode* modelRoot,
416 const osgDB::ReaderWriter::Options*
418 SGAnimation(configNode, modelRoot),
419 texturePathList(options->getDatabasePathList())
421 if (configNode->hasChild("global"))
422 SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
423 "no longer supported");
427 SGMaterialAnimation::createAnimationGroup(osg::Group& parent)
429 osg::Group* group = new osg::Group;
430 group->setName("material animation group");
432 SGPropertyNode* inputRoot = getModelRoot();
433 const SGPropertyNode* node = getConfig()->getChild("property-base");
435 inputRoot = getModelRoot()->getNode(node->getStringValue(), true);
436 osg::StateSet* stateSet = group->getOrCreateStateSet();
437 if (getConfig()->hasChild("texture")) {
438 std::string textureName = getConfig()->getStringValue("texture");
439 std::string textureFile;
440 textureFile = osgDB::findFileInPath(textureName, texturePathList);
441 if (!textureFile.empty()) {
442 osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
444 stateSet->setTextureAttribute(0, texture2D,
445 osg::StateAttribute::OVERRIDE);
446 stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
447 if (texture2D->getImage()->isImageTranslucent()) {
448 stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
449 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
454 if (getConfig()->hasChild("threshold-prop") ||
455 getConfig()->hasChild("threshold")) {
456 osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
457 alphaFunc->setFunction(osg::AlphaFunc::GREATER);
458 float threshold = getConfig()->getFloatValue("threshold", 0);
459 alphaFunc->setReferenceValue(threshold);
460 stateSet->setAttribute(alphaFunc, osg::StateAttribute::OVERRIDE);
463 unsigned suppliedColors = 0;
464 if (getConfig()->hasChild("ambient"))
465 suppliedColors |= AMBIENT;
466 if (getConfig()->hasChild("diffuse"))
467 suppliedColors |= DIFFUSE;
468 if (getConfig()->hasChild("specular"))
469 suppliedColors |= SPECULAR;
470 if (getConfig()->hasChild("emission"))
471 suppliedColors |= EMISSION;
472 if (getConfig()->hasChild("shininess")
473 || getConfig()->hasChild("shininess-prop"))
474 suppliedColors |= SHININESS;
475 if (getConfig()->hasChild("transparency"))
476 suppliedColors |= TRANSPARENCY;
477 osg::Material* mat = 0;
478 if (suppliedColors != 0) {
479 if (defaultMaterial.valid()) {
480 mat = defaultMaterial.get();
483 mat = new osg::Material;
484 mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
486 mat->setDataVariance(osg::Object::DYNAMIC);
487 unsigned defaultColorModeMask = 0;
488 mat->setUpdateCallback(0); // Just to make sure.
489 // XXX This should probably go away, as ac3d models always have a
490 // DIFFUSE color mode.
491 switch (mat->getColorMode()) {
492 case osg::Material::OFF:
493 defaultColorModeMask = 0;
495 case osg::Material::AMBIENT:
496 defaultColorModeMask = AMBIENT;
498 case osg::Material::DIFFUSE:
499 defaultColorModeMask = DIFFUSE;
501 case osg::Material::AMBIENT_AND_DIFFUSE:
502 defaultColorModeMask = AMBIENT | DIFFUSE;
504 case osg::Material::SPECULAR:
505 defaultColorModeMask = SPECULAR;
507 case osg::Material::EMISSION:
508 defaultColorModeMask = EMISSION;
511 // Copy the color found by traversing geometry into the material
512 // in case we need to specify it (e.g., transparency) and it is
513 // not specified by the animation.
514 if (defaultAmbientDiffuse.x() >= 0) {
515 if (defaultColorModeMask & AMBIENT)
516 mat->setAmbient(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
517 if (defaultColorModeMask & DIFFUSE)
518 mat->setDiffuse(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
520 // Compute which colors in the animation override colors set via
521 // colorMode / glColor, and set the colorMode for the animation's
522 // material accordingly.
523 if (suppliedColors & TRANSPARENCY) {
524 // All colors will be affected by the material. Hope all the
525 // defaults are fine, if needed.
526 mat->setColorMode(osg::Material::OFF);
527 } else if ((suppliedColors & defaultColorModeMask) != 0) {
528 // First deal with the complicated AMBIENT/DIFFUSE case.
529 if ((defaultColorModeMask & AMBIENT_DIFFUSE) != 0) {
530 // glColor can supply colors not specified by the animation.
531 unsigned matColorModeMask = ((~suppliedColors & defaultColorModeMask)
533 if ((matColorModeMask & DIFFUSE) != 0)
534 mat->setColorMode(osg::Material::DIFFUSE);
535 else if ((matColorModeMask & AMBIENT) != 0)
536 mat->setColorMode(osg::Material::AMBIENT);
538 mat->setColorMode(osg::Material::OFF);
540 // The animation overrides the glColor color.
541 mat->setColorMode(osg::Material::OFF);
544 // No overlap between the animation and color mode, so leave
545 // the color mode alone.
547 stateSet->setAttribute(mat,(osg::StateAttribute::ON
548 | osg::StateAttribute::OVERRIDE));
550 bool matAnimated = false;
552 MaterialPropertyAdapter adapter(getConfig(), inputRoot);
553 adapter.setMaterialValues(stateSet);
554 matAnimated = adapter.isAnimated();
556 if (matAnimated || getConfig()->hasChild("texture-prop")
557 || getConfig()->hasChild("threshold-prop") || getCondition()) {
558 stateSet->setDataVariance(osg::Object::DYNAMIC);
559 group->setUpdateCallback(new UpdateCallback(texturePathList,
561 getConfig(), inputRoot));
563 stateSet->setDataVariance(osg::Object::STATIC);
565 parent.addChild(group);
570 SGMaterialAnimation::install(osg::Node& node)
572 SGAnimation::install(node);
574 MaterialDefaultsVisitor defaultsVisitor;
575 node.accept(defaultsVisitor);
576 if (defaultsVisitor.material.valid()) {
578 = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
580 defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;
583 const char* colorNames[] =
591 // Build an effect which mimics the material color mode in a
592 // shader. The OpenGL material values will be overridden by the
593 // material animation's material.
595 // This is a hack to get the effect to respect the values set in the
596 // material, set up by the animation, which overrides the values in
597 // the effect's material attributes. Things will be different when
598 // material animations are implemented purely by manipulating effects.
601 SGMaterialAnimation::makeEffectProperties(const SGPropertyNode* animProp)
603 SGPropertyNode_ptr eRoot = new SGPropertyNode;
604 SGPropertyNode* inherit = makeNode(eRoot, "inherits-from");
605 if (animProp->hasChild("diffuse") || animProp->hasChild("transparency"))
606 inherit->setStringValue("Effects/material-off");
608 inherit->setStringValue("Effects/material-diffuse");