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/model/model.hxx>
29 * Get a color from properties.
32 float red, green, blue;
35 SGPropertyNode_ptr red_prop;
36 SGPropertyNode_ptr green_prop;
37 SGPropertyNode_ptr blue_prop;
38 SGPropertyNode_ptr factor_prop;
39 SGPropertyNode_ptr offset_prop;
42 ColorSpec(const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
50 red = configNode->getFloatValue("red", -1.0);
51 green = configNode->getFloatValue("green", -1.0);
52 blue = configNode->getFloatValue("blue", -1.0);
53 factor = configNode->getFloatValue("factor", 1.0);
54 offset = configNode->getFloatValue("offset", 0.0);
58 const SGPropertyNode *node;
59 node = configNode->getChild("red-prop");
61 red_prop = modelRoot->getNode(node->getStringValue(), true);
62 node = configNode->getChild("green-prop");
64 green_prop = modelRoot->getNode(node->getStringValue(), true);
65 node = configNode->getChild("blue-prop");
67 blue_prop = modelRoot->getNode(node->getStringValue(), true);
68 node = configNode->getChild("factor-prop");
70 factor_prop = modelRoot->getNode(node->getStringValue(), true);
71 node = configNode->getChild("offset-prop");
73 offset_prop = modelRoot->getNode(node->getStringValue(), true);
77 return red >= 0 || green >= 0 || blue >= 0;
80 return red_prop || green_prop || blue_prop
81 || factor_prop || offset_prop;
85 red = red_prop->getFloatValue();
87 green = green_prop->getFloatValue();
89 blue = blue_prop->getFloatValue();
91 factor = factor_prop->getFloatValue();
93 offset = offset_prop->getFloatValue();
94 v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
95 v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
96 v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
103 return toOsg(rgba());
106 SGVec4f &initialRgba() {
107 v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
108 v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
109 v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
116 * Get a property value from a property.
124 SGPropertyNode_ptr value_prop;
125 SGPropertyNode_ptr factor_prop;
126 SGPropertyNode_ptr offset_prop;
128 PropSpec(const char* valueName, const char* valuePropName,
129 const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
135 value = configNode->getFloatValue(valueName, -1);
136 factor = configNode->getFloatValue("factor", 1);
137 offset = configNode->getFloatValue("offset", 0);
138 min = configNode->getFloatValue("min", 0);
139 max = configNode->getFloatValue("max", 1);
143 const SGPropertyNode *node;
144 node = configNode->getChild(valuePropName);
146 value_prop = modelRoot->getNode(node->getStringValue(), true);
147 node = configNode->getChild("factor-prop");
149 factor_prop = modelRoot->getNode(node->getStringValue(), true);
150 node = configNode->getChild("offset-prop");
152 offset_prop = modelRoot->getNode(node->getStringValue(), true);
154 bool dirty() { return value >= 0.0; }
155 bool live() { return value_prop || factor_prop || offset_prop; }
159 value = value_prop->getFloatValue();
161 offset = offset_prop->getFloatValue();
163 factor = factor_prop->getFloatValue();
164 return SGMiscf::clip(value*factor + offset, min, max);
166 float getInitialValue()
168 return SGMiscf::clip(value*factor + offset, min, max);
173 * The possible color properties supplied by a material animation.
184 const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
186 const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
189 // Visitor for finding default material colors in the animation node's
190 // subgraph. This makes some assumptions about the subgraph i.e.,
191 // there will be one material and one color value found. This is
192 // probably true for ac3d models and most uses of material animations,
193 // but will break down if, for example, you animate the transparency
194 // of a vertex colored model.
195 class MaterialDefaultsVisitor : public osg::NodeVisitor {
197 MaterialDefaultsVisitor()
198 : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
199 ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
201 setVisitorType(osg::NodeVisitor::NODE_VISITOR);
204 virtual void apply(osg::Node& node)
206 maybeGetMaterialValues(node.getStateSet());
210 virtual void apply(osg::Geode& node)
212 maybeGetMaterialValues(node.getStateSet());
213 int numDrawables = node.getNumDrawables();
214 for (int i = 0; i < numDrawables; i++) {
215 osg::Geometry* geom = dynamic_cast<osg::Geometry*>(node.getDrawable(i));
216 if (!geom || geom->getColorBinding() != osg::Geometry::BIND_OVERALL)
218 maybeGetMaterialValues(geom->getStateSet());
219 osg::Array* colorArray = geom->getColorArray();
220 osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
222 ambientDiffuse = (*colorVec4)[0];
225 osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
227 ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
233 void maybeGetMaterialValues(osg::StateSet* stateSet)
237 osg::Material* nodeMat
238 = dynamic_cast<osg::Material*>(stateSet->getAttribute(osg::StateAttribute::MATERIAL));
244 osg::ref_ptr<osg::Material> material;
245 osg::Vec4 ambientDiffuse;
248 class MaterialPropertyAdapter
251 MaterialPropertyAdapter(const SGPropertyNode* configNode,
252 SGPropertyNode* modelRoot) :
253 _ambient(configNode->getChild("ambient"), modelRoot),
254 _diffuse(configNode->getChild("diffuse"), modelRoot),
255 _specular(configNode->getChild("specular"), modelRoot),
256 _emission(configNode->getChild("emission"), modelRoot),
257 _shininess("shininess", "shininess-prop",
258 configNode/*->getChild("shininess")*/, modelRoot),
259 _transparency("alpha", "alpha-prop",
260 configNode->getChild("transparency"), modelRoot)
262 _shininess.max = 128;
263 _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
264 || _emission.live() || _shininess.live()
265 || _transparency.live());
267 bool isAnimated() { return _isAnimated; }
268 // This takes a StateSet argument because the rendering bin will
269 // be changed if there is transparency.
270 void setMaterialValues(osg::StateSet* stateSet)
272 osg::StateAttribute* stateAttribute
273 = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
274 osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
276 if (_ambient.live() || _ambient.dirty())
277 material->setAmbient(osg::Material::FRONT_AND_BACK,
278 _ambient.rgbaVec4());
279 if (_diffuse.live() || _diffuse.dirty())
280 material->setDiffuse(osg::Material::FRONT_AND_BACK,
281 _diffuse.rgbaVec4());
282 if (_specular.live() || _specular.dirty())
283 material->setSpecular(osg::Material::FRONT_AND_BACK,
284 _specular.rgbaVec4());
285 if (_emission.live() || _emission.dirty())
286 material->setEmission(osg::Material::FRONT_AND_BACK,
287 _emission.rgbaVec4());
288 if (_shininess.live() || _shininess.dirty())
289 material->setShininess(osg::Material::FRONT_AND_BACK,
290 _shininess.getValue());
291 if (_transparency.live() || _transparency.dirty()) {
292 float alpha = _transparency.getValue();
293 material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
295 stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
296 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
298 stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
308 PropSpec _transparency;
313 class UpdateCallback : public osg::NodeCallback {
315 UpdateCallback(const osgDB::FilePathList& texturePathList,
316 const SGCondition* condition,
317 const SGPropertyNode* configNode, SGPropertyNode* modelRoot) :
318 _condition(condition),
319 _materialProps(configNode, modelRoot),
320 _texturePathList(texturePathList),
323 const SGPropertyNode* node;
325 node = configNode->getChild("threshold-prop");
327 _thresholdProp = modelRoot->getNode(node->getStringValue(), true);
328 node = configNode->getChild("texture-prop");
330 _textureProp = modelRoot->getNode(node->getStringValue(), true);
333 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
335 osg::StateSet* stateSet = node->getStateSet();
336 if ((!_condition || _condition->test()) && stateSet) {
338 std::string textureName = _textureProp->getStringValue();
339 if (_textureName != textureName) {
340 while (stateSet->getTextureAttribute(0,
341 osg::StateAttribute::TEXTURE)) {
342 stateSet->removeTextureAttribute(0, osg::StateAttribute::TEXTURE);
344 std::string textureFile;
345 textureFile = osgDB::findFileInPath(textureName, _texturePathList);
346 if (!textureFile.empty()) {
347 osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
349 stateSet->setTextureAttribute(0, texture2D,
350 osg::StateAttribute::OVERRIDE);
351 stateSet->setTextureMode(0, GL_TEXTURE_2D,
352 osg::StateAttribute::ON);
353 _textureName = textureName;
358 if (_thresholdProp) {
359 osg::StateSet* stateSet = node->getOrCreateStateSet();
360 osg::StateAttribute* stateAttribute;
361 stateAttribute = stateSet->getAttribute(osg::StateAttribute::ALPHAFUNC);
362 osg::AlphaFunc* alphaFunc
363 = dynamic_cast<osg::AlphaFunc*>(stateAttribute);
365 alphaFunc->setReferenceValue(_thresholdProp->getFloatValue());
367 if (_materialProps.isAnimated() || !_prevState)
368 _materialProps.setMaterialValues(stateSet);
376 SGSharedPtr<const SGCondition> _condition;
377 SGSharedPtr<const SGPropertyNode> _textureProp;
378 SGSharedPtr<const SGPropertyNode> _thresholdProp;
379 std::string _textureName;
380 MaterialPropertyAdapter _materialProps;
381 osgDB::FilePathList _texturePathList;
387 SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
388 SGPropertyNode* modelRoot,
389 const osgDB::ReaderWriter::Options*
391 SGAnimation(configNode, modelRoot),
392 texturePathList(options->getDatabasePathList())
394 if (configNode->hasChild("global"))
395 SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
396 "no longer supported");
400 SGMaterialAnimation::createAnimationGroup(osg::Group& parent)
402 osg::Group* group = new osg::Group;
403 group->setName("material animation group");
405 SGPropertyNode* inputRoot = getModelRoot();
406 const SGPropertyNode* node = getConfig()->getChild("property-base");
408 inputRoot = getModelRoot()->getNode(node->getStringValue(), true);
409 osg::StateSet* stateSet = group->getOrCreateStateSet();
410 if (getConfig()->hasChild("texture")) {
411 std::string textureName = getConfig()->getStringValue("texture");
412 std::string textureFile;
413 textureFile = osgDB::findFileInPath(textureName, texturePathList);
414 if (!textureFile.empty()) {
415 osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
417 stateSet->setTextureAttribute(0, texture2D,
418 osg::StateAttribute::OVERRIDE);
419 stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
420 if (texture2D->getImage()->isImageTranslucent()) {
421 stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
422 stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
427 if (getConfig()->hasChild("threshold-prop") ||
428 getConfig()->hasChild("threshold")) {
429 osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
430 alphaFunc->setFunction(osg::AlphaFunc::GREATER);
431 float threshold = getConfig()->getFloatValue("threshold", 0);
432 alphaFunc->setReferenceValue(threshold);
433 stateSet->setAttribute(alphaFunc, osg::StateAttribute::OVERRIDE);
436 unsigned suppliedColors = 0;
437 if (getConfig()->hasChild("ambient"))
438 suppliedColors |= AMBIENT;
439 if (getConfig()->hasChild("diffuse"))
440 suppliedColors |= DIFFUSE;
441 if (getConfig()->hasChild("specular"))
442 suppliedColors |= SPECULAR;
443 if (getConfig()->hasChild("emission"))
444 suppliedColors |= EMISSION;
445 if (getConfig()->hasChild("shininess")
446 || getConfig()->hasChild("shininess-prop"))
447 suppliedColors |= SHININESS;
448 if (getConfig()->hasChild("transparency"))
449 suppliedColors |= TRANSPARENCY;
450 osg::Material* mat = 0;
451 if (suppliedColors != 0) {
452 if (defaultMaterial.valid()) {
453 mat = defaultMaterial.get();
456 mat = new osg::Material;
457 mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
459 mat->setDataVariance(osg::Object::DYNAMIC);
460 unsigned defaultColorModeMask = 0;
461 mat->setUpdateCallback(0); // Just to make sure.
462 switch (mat->getColorMode()) {
463 case osg::Material::OFF:
464 defaultColorModeMask = 0;
466 case osg::Material::AMBIENT:
467 defaultColorModeMask = AMBIENT;
469 case osg::Material::DIFFUSE:
470 defaultColorModeMask = DIFFUSE;
472 case osg::Material::AMBIENT_AND_DIFFUSE:
473 defaultColorModeMask = AMBIENT | DIFFUSE;
475 case osg::Material::SPECULAR:
476 defaultColorModeMask = SPECULAR;
478 case osg::Material::EMISSION:
479 defaultColorModeMask = EMISSION;
482 // Copy the color found by traversing geometry into the material
483 // in case we need to specify it (e.g., transparency) and it is
484 // not specified by the animation.
485 if (defaultAmbientDiffuse.x() >= 0) {
486 if (defaultColorModeMask & AMBIENT)
487 mat->setAmbient(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
488 if (defaultColorModeMask & DIFFUSE)
489 mat->setDiffuse(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
491 // Compute which colors in the animation override colors set via
492 // colorMode / glColor, and set the colorMode for the animation's
493 // material accordingly.
494 if (suppliedColors & TRANSPARENCY) {
495 // All colors will be affected by the material. Hope all the
496 // defaults are fine, if needed.
497 mat->setColorMode(osg::Material::OFF);
498 } else if ((suppliedColors & defaultColorModeMask) != 0) {
499 // First deal with the complicated AMBIENT/DIFFUSE case.
500 if ((defaultColorModeMask & AMBIENT_DIFFUSE) != 0) {
501 // glColor can supply colors not specified by the animation.
502 unsigned matColorModeMask = ((~suppliedColors & defaultColorModeMask)
504 if ((matColorModeMask & DIFFUSE) != 0)
505 mat->setColorMode(osg::Material::DIFFUSE);
506 else if ((matColorModeMask & AMBIENT) != 0)
507 mat->setColorMode(osg::Material::AMBIENT);
509 mat->setColorMode(osg::Material::OFF);
511 // The animation overrides the glColor color.
512 mat->setColorMode(osg::Material::OFF);
515 // No overlap between the animation and color mode, so leave
516 // the color mode alone.
518 stateSet->setAttribute(mat,(osg::StateAttribute::ON
519 | osg::StateAttribute::OVERRIDE));
521 bool matAnimated = false;
523 MaterialPropertyAdapter adapter(getConfig(), inputRoot);
524 adapter.setMaterialValues(stateSet);
525 matAnimated = adapter.isAnimated();
527 if (matAnimated || getConfig()->hasChild("texture-prop")
528 || getConfig()->hasChild("threshold-prop") || getCondition()) {
529 stateSet->setDataVariance(osg::Object::DYNAMIC);
530 group->setUpdateCallback(new UpdateCallback(texturePathList,
532 getConfig(), inputRoot));
534 stateSet->setDataVariance(osg::Object::STATIC);
536 parent.addChild(group);
541 SGMaterialAnimation::install(osg::Node& node)
543 SGAnimation::install(node);
545 MaterialDefaultsVisitor defaultsVisitor;
546 node.accept(defaultsVisitor);
547 if (defaultsVisitor.material.valid()) {
549 = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
551 defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;