]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGMaterialAnimation.cxx
Merge branch 'ehofman/sound'
[simgear.git] / simgear / scene / model / SGMaterialAnimation.cxx
1 // animation.cxx - classes to manage model animation.
2 // Written by David Megginson, started 2002.
3 //
4 // This file is in the Public Domain, and comes with no warranty.
5
6 #ifdef HAVE_CONFIG_H
7 #  include <simgear_config.h>
8 #endif
9
10 #include "SGMaterialAnimation.hxx"
11
12 #include <osg/AlphaFunc>
13 #include <osg/Array>
14 #include <osg/Drawable>
15 #include <osg/Geode>
16 #include <osg/Geometry>
17 #include <osg/Material>
18 #include <osg/StateSet>
19 #include <osgDB/FileNameUtils>
20 #include <osgDB/FileUtils>
21 #include <osgDB/ReadFile>
22
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>
30
31 using namespace std;
32
33 namespace {
34 /**
35  * Get a color from properties.
36  */
37 struct ColorSpec {
38   float red, green, blue;
39   float factor;
40   float offset;
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;
46   SGVec4f v;
47   
48   ColorSpec(const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
49   {
50     red = -1.0;
51     green = -1.0;
52     blue = -1.0;
53     if (!configNode)
54       return;
55     
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);
61     
62     if (!modelRoot)
63       return;
64     const SGPropertyNode *node;
65     node = configNode->getChild("red-prop");
66     if (node)
67       red_prop = modelRoot->getNode(node->getStringValue(), true);
68     node = configNode->getChild("green-prop");
69     if (node)
70       green_prop = modelRoot->getNode(node->getStringValue(), true);
71     node = configNode->getChild("blue-prop");
72     if (node)
73       blue_prop = modelRoot->getNode(node->getStringValue(), true);
74     node = configNode->getChild("factor-prop");
75     if (node)
76       factor_prop = modelRoot->getNode(node->getStringValue(), true);
77     node = configNode->getChild("offset-prop");
78     if (node)
79       offset_prop = modelRoot->getNode(node->getStringValue(), true);
80   }
81   
82   bool dirty() {
83     return red >= 0 || green >= 0 || blue >= 0;
84   }
85   bool live() {
86     return red_prop || green_prop || blue_prop
87       || factor_prop || offset_prop;
88   }
89   SGVec4f &rgba() {
90     if (red_prop)
91       red = red_prop->getFloatValue();
92     if (green_prop)
93       green = green_prop->getFloatValue();
94     if (blue_prop)
95       blue = blue_prop->getFloatValue();
96     if (factor_prop)
97       factor = factor_prop->getFloatValue();
98     if (offset_prop)
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);
103     v[3] = 1;
104     return v;
105   }
106
107   osg::Vec4 rgbaVec4()
108   {
109     return toOsg(rgba());
110   }
111   
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);
116     v[3] = 1;
117     return v;
118   }
119 };
120
121 /**
122  * Get a property value from a property.
123  */
124 struct PropSpec {
125   float value;
126   float factor;
127   float offset;
128   float min;
129   float max;
130   SGPropertyNode_ptr value_prop;
131   SGPropertyNode_ptr factor_prop;
132   SGPropertyNode_ptr offset_prop;
133   
134   PropSpec(const char* valueName, const char* valuePropName,
135            const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
136   {
137     value = -1;
138     if (!configNode)
139       return;
140     
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);
146     
147     if (!modelRoot)
148       return;
149     const SGPropertyNode *node;
150     node = configNode->getChild(valuePropName);
151     if (node)
152       value_prop = modelRoot->getNode(node->getStringValue(), true);
153     node = configNode->getChild("factor-prop");
154     if (node)
155       factor_prop = modelRoot->getNode(node->getStringValue(), true);
156     node = configNode->getChild("offset-prop");
157     if (node)
158       offset_prop = modelRoot->getNode(node->getStringValue(), true);
159   }
160   bool dirty() { return value >= 0.0; }
161   bool live() { return value_prop || factor_prop || offset_prop; }
162   float getValue()
163   {
164     if (value_prop)
165       value = value_prop->getFloatValue();
166     if (offset_prop)
167       offset = offset_prop->getFloatValue();
168     if (factor_prop)
169       factor = factor_prop->getFloatValue();
170     return SGMiscf::clip(value*factor + offset, min, max);
171   }
172   float getInitialValue()
173   {
174     return SGMiscf::clip(value*factor + offset, min, max);
175   }
176 };
177
178 /**
179  * The possible color properties supplied by a material animation.
180  */
181 enum SuppliedColor {
182   DIFFUSE = 1,
183   AMBIENT = 2,
184   SPECULAR = 4,
185   EMISSION = 8,
186   SHININESS = 16,
187   TRANSPARENCY = 32
188 };
189
190 const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
191
192 const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
193                                | SHININESS);
194
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 {
202 public:
203   MaterialDefaultsVisitor()
204     : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
205       ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
206   {
207     setVisitorType(osg::NodeVisitor::NODE_VISITOR);
208   }
209
210   virtual void apply(osg::Node& node)
211   {
212     maybeGetMaterialValues(node.getStateSet());
213     traverse(node);
214   }
215
216   virtual void apply(osg::Geode& node)
217   {
218     using namespace simgear;
219     EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
220     if (eg) {
221       const Effect* effect = eg->getEffect();
222       if (effect)
223         for (vector<osg::ref_ptr<Technique> >::const_iterator itr
224                = effect->techniques.begin(), end = effect->techniques.end();
225              itr != end;
226              ++itr) {
227           const Technique* tniq = itr->get();
228           for (vector<osg::ref_ptr<Pass> >::const_iterator pitr
229                  = tniq->passes.begin(), pend = tniq->passes.end();
230                pitr != pend;
231                ++pitr)
232             maybeGetMaterialValues(pitr->get());
233         }
234     } else {
235       maybeGetMaterialValues(node.getStateSet());
236     }
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)
241         continue;
242       maybeGetMaterialValues(geom->getStateSet());
243       osg::Array* colorArray = geom->getColorArray();
244       osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
245       if (colorVec4) {
246         ambientDiffuse = (*colorVec4)[0];
247         break;
248       }
249       osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
250       if (colorVec3) {
251         ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
252         break;
253       }
254     }
255   }
256   
257   void maybeGetMaterialValues(const osg::StateSet* stateSet)
258   {
259     if (!stateSet)
260       return;
261     const osg::Material* nodeMat
262       = dynamic_cast<const osg::Material*>(stateSet
263                                            ->getAttribute(osg::StateAttribute
264                                                           ::MATERIAL));
265     if (!nodeMat)
266       return;
267     material = nodeMat;
268   }
269
270   osg::ref_ptr<const osg::Material> material;
271   osg::Vec4 ambientDiffuse;
272 };
273
274 class MaterialPropertyAdapter
275 {
276 public:
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)
287     {
288         _shininess.max = 128;
289         _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
290                        || _emission.live() || _shininess.live()
291                        || _transparency.live());
292     }
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)
297     {
298         osg::StateAttribute* stateAttribute
299             = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
300         osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
301         if (material) {
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);
320                 if (alpha < 1.0f) {
321                     stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
322                     stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
323                 } else {
324                     stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
325                 }
326             }
327         }
328     }
329     ColorSpec _ambient;
330     ColorSpec _diffuse;
331     ColorSpec _specular;
332     ColorSpec _emission;
333     PropSpec _shininess;
334     PropSpec _transparency;
335     bool _isAnimated;
336
337 };
338
339 class UpdateCallback : public osg::NodeCallback {
340 public:
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),
347     _prevState(false)
348   {
349     const SGPropertyNode* node;
350
351     node = configNode->getChild("threshold-prop");
352     if (node)
353       _thresholdProp = modelRoot->getNode(node->getStringValue(), true);
354     node = configNode->getChild("texture-prop");
355     if (node)
356       _textureProp = modelRoot->getNode(node->getStringValue(), true);
357   }
358
359   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
360   {
361     osg::StateSet* stateSet = node->getStateSet();
362     if ((!_condition || _condition->test()) && stateSet) {
363       if (_textureProp) {
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);
369           }
370           std::string textureFile;
371           textureFile = osgDB::findFileInPath(textureName, _texturePathList);
372           if (!textureFile.empty()) {
373             osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
374             if (texture2D) {
375               stateSet->setTextureAttribute(0, texture2D,
376                                             osg::StateAttribute::OVERRIDE);
377               stateSet->setTextureMode(0, GL_TEXTURE_2D,
378                                        osg::StateAttribute::ON);
379               _textureName = textureName;
380             }
381           }
382         }
383       }
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);
390         assert(alphaFunc);
391         alphaFunc->setReferenceValue(_thresholdProp->getFloatValue());
392       }
393       if (_materialProps.isAnimated() || !_prevState)
394           _materialProps.setMaterialValues(stateSet);
395       _prevState = true;
396     } else {
397       _prevState = false;
398     }
399     traverse(node, nv);
400   }
401 private:
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;
408   bool _prevState;
409 };
410 } // namespace
411
412
413 SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
414                                          SGPropertyNode* modelRoot,
415                                          const osgDB::ReaderWriter::Options*
416                                          options) :
417   SGAnimation(configNode, modelRoot),
418   texturePathList(options->getDatabasePathList())
419 {
420   if (configNode->hasChild("global"))
421     SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
422            "no longer supported");
423 }
424
425 osg::Group*
426 SGMaterialAnimation::createAnimationGroup(osg::Group& parent)
427 {
428   osg::Group* group = new osg::Group;
429   group->setName("material animation group");
430
431   SGPropertyNode* inputRoot = getModelRoot();
432   const SGPropertyNode* node = getConfig()->getChild("property-base");
433   if (node)
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);
442       if (texture2D) {
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);
449         }
450       }
451     }
452   }
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);
460   }
461
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();
480
481     } else {
482       mat = new osg::Material;
483       mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
484     }
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;
491       break;
492     case osg::Material::AMBIENT:
493       defaultColorModeMask = AMBIENT;
494       break;
495     case osg::Material::DIFFUSE:
496       defaultColorModeMask = DIFFUSE;
497       break;
498     case osg::Material::AMBIENT_AND_DIFFUSE:
499       defaultColorModeMask = AMBIENT | DIFFUSE;
500       break;
501     case osg::Material::SPECULAR:
502       defaultColorModeMask = SPECULAR;
503       break;
504     case osg::Material::EMISSION:
505       defaultColorModeMask = EMISSION;
506       break;
507     }
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);
516     }
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)
529                                      & AMBIENT_DIFFUSE);
530         if ((matColorModeMask & DIFFUSE) != 0)
531           mat->setColorMode(osg::Material::DIFFUSE);
532         else if ((matColorModeMask & AMBIENT) != 0)
533           mat->setColorMode(osg::Material::AMBIENT);
534         else
535           mat->setColorMode(osg::Material::OFF);
536       } else {
537         // The animation overrides the glColor color.
538         mat->setColorMode(osg::Material::OFF);
539       }
540     } else {
541       // No overlap between the animation and color mode, so leave
542       // the color mode alone.
543     }
544     stateSet->setAttribute(mat,(osg::StateAttribute::ON
545                                 | osg::StateAttribute::OVERRIDE));
546   }
547   bool matAnimated = false;
548   if (mat) {
549     MaterialPropertyAdapter adapter(getConfig(), inputRoot);
550     adapter.setMaterialValues(stateSet);
551     matAnimated = adapter.isAnimated();
552   }
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,
557                                                 getCondition(),
558                                                 getConfig(), inputRoot));
559   } else {
560     stateSet->setDataVariance(osg::Object::STATIC);
561   }
562   parent.addChild(group);
563   return group;
564 }
565
566 void
567 SGMaterialAnimation::install(osg::Node& node)
568 {
569   SGAnimation::install(node);
570
571     MaterialDefaultsVisitor defaultsVisitor;
572     node.accept(defaultsVisitor);
573     if (defaultsVisitor.material.valid()) {
574       defaultMaterial
575         = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
576     }
577     defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;
578 }