]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGMaterialAnimation.cxx
Merge branch 'maint' into next
[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/model/model.hxx>
26
27 namespace {
28 /**
29  * Get a color from properties.
30  */
31 struct ColorSpec {
32   float red, green, blue;
33   float factor;
34   float offset;
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;
40   SGVec4f v;
41   
42   ColorSpec(const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
43   {
44     red = -1.0;
45     green = -1.0;
46     blue = -1.0;
47     if (!configNode)
48       return;
49     
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);
55     
56     if (!modelRoot)
57       return;
58     const SGPropertyNode *node;
59     node = configNode->getChild("red-prop");
60     if (node)
61       red_prop = modelRoot->getNode(node->getStringValue(), true);
62     node = configNode->getChild("green-prop");
63     if (node)
64       green_prop = modelRoot->getNode(node->getStringValue(), true);
65     node = configNode->getChild("blue-prop");
66     if (node)
67       blue_prop = modelRoot->getNode(node->getStringValue(), true);
68     node = configNode->getChild("factor-prop");
69     if (node)
70       factor_prop = modelRoot->getNode(node->getStringValue(), true);
71     node = configNode->getChild("offset-prop");
72     if (node)
73       offset_prop = modelRoot->getNode(node->getStringValue(), true);
74   }
75   
76   bool dirty() {
77     return red >= 0 || green >= 0 || blue >= 0;
78   }
79   bool live() {
80     return red_prop || green_prop || blue_prop
81       || factor_prop || offset_prop;
82   }
83   SGVec4f &rgba() {
84     if (red_prop)
85       red = red_prop->getFloatValue();
86     if (green_prop)
87       green = green_prop->getFloatValue();
88     if (blue_prop)
89       blue = blue_prop->getFloatValue();
90     if (factor_prop)
91       factor = factor_prop->getFloatValue();
92     if (offset_prop)
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);
97     v[3] = 1;
98     return v;
99   }
100
101   osg::Vec4& rgbaVec4()
102   {
103     return rgba().osg();
104   }
105   
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);
110     v[3] = 1;
111     return v;
112   }
113 };
114
115 /**
116  * Get a property value from a property.
117  */
118 struct PropSpec {
119   float value;
120   float factor;
121   float offset;
122   float min;
123   float max;
124   SGPropertyNode_ptr value_prop;
125   SGPropertyNode_ptr factor_prop;
126   SGPropertyNode_ptr offset_prop;
127   
128   PropSpec(const char* valueName, const char* valuePropName,
129            const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
130   {
131     value = -1;
132     if (!configNode)
133       return;
134     
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);
140     
141     if (!modelRoot)
142       return;
143     const SGPropertyNode *node;
144     node = configNode->getChild(valuePropName);
145     if (node)
146       value_prop = modelRoot->getNode(node->getStringValue(), true);
147     node = configNode->getChild("factor-prop");
148     if (node)
149       factor_prop = modelRoot->getNode(node->getStringValue(), true);
150     node = configNode->getChild("offset-prop");
151     if (node)
152       offset_prop = modelRoot->getNode(node->getStringValue(), true);
153   }
154   bool dirty() { return value >= 0.0; }
155   bool live() { return value_prop || factor_prop || offset_prop; }
156   float getValue()
157   {
158     if (value_prop)
159       value = value_prop->getFloatValue();
160     if (offset_prop)
161       offset = offset_prop->getFloatValue();
162     if (factor_prop)
163       factor = factor_prop->getFloatValue();
164     return SGMiscf::clip(value*factor + offset, min, max);
165   }
166   float getInitialValue()
167   {
168     return SGMiscf::clip(value*factor + offset, min, max);
169   }
170 };
171
172 /**
173  * The possible color properties supplied by a material animation.
174  */
175 enum SuppliedColor {
176   DIFFUSE = 1,
177   AMBIENT = 2,
178   SPECULAR = 4,
179   EMISSION = 8,
180   SHININESS = 16,
181   TRANSPARENCY = 32
182 };
183
184 const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
185
186 const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
187                                | SHININESS);
188
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 {
196 public:
197   MaterialDefaultsVisitor()
198     : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
199       ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
200   {
201     setVisitorType(osg::NodeVisitor::NODE_VISITOR);
202   }
203
204   virtual void apply(osg::Node& node)
205   {
206     maybeGetMaterialValues(node.getStateSet());
207     traverse(node);
208   }
209
210   virtual void apply(osg::Geode& node)
211   {
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)
217         continue;
218       maybeGetMaterialValues(geom->getStateSet());
219       osg::Array* colorArray = geom->getColorArray();
220       osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
221       if (colorVec4) {
222         ambientDiffuse = (*colorVec4)[0];
223         break;
224       }
225       osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
226       if (colorVec3) {
227         ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
228         break;
229       }
230     }
231   }
232   
233   void maybeGetMaterialValues(osg::StateSet* stateSet)
234   {
235     if (!stateSet)
236       return;
237     osg::Material* nodeMat
238       = dynamic_cast<osg::Material*>(stateSet->getAttribute(osg::StateAttribute::MATERIAL));
239     if (!nodeMat)
240       return;
241     material = nodeMat;
242   }
243
244   osg::ref_ptr<osg::Material> material;
245   osg::Vec4 ambientDiffuse;
246 };
247
248 class MaterialPropertyAdapter
249 {
250 public:
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)
261     {
262         _shininess.max = 128;
263         _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
264                        || _emission.live() || _shininess.live()
265                        || _transparency.live());
266     }
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)
271     {
272         osg::StateAttribute* stateAttribute
273             = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
274         osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
275         if (material) {
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);
294                 if (alpha < 1.0f) {
295                     stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
296                     stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
297                 } else {
298                     stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
299                 }
300             }
301         }
302     }
303     ColorSpec _ambient;
304     ColorSpec _diffuse;
305     ColorSpec _specular;
306     ColorSpec _emission;
307     PropSpec _shininess;
308     PropSpec _transparency;
309     bool _isAnimated;
310
311 };
312
313 class UpdateCallback : public osg::NodeCallback {
314 public:
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),
321     _prevState(false)
322   {
323     const SGPropertyNode* node;
324
325     node = configNode->getChild("threshold-prop");
326     if (node)
327       _thresholdProp = modelRoot->getNode(node->getStringValue(), true);
328     node = configNode->getChild("texture-prop");
329     if (node)
330       _textureProp = modelRoot->getNode(node->getStringValue(), true);
331   }
332
333   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
334   {
335     osg::StateSet* stateSet = node->getStateSet();
336     if ((!_condition || _condition->test()) && stateSet) {
337       if (_textureProp) {
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);
343           }
344           std::string textureFile;
345           textureFile = osgDB::findFileInPath(textureName, _texturePathList);
346           if (!textureFile.empty()) {
347             osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
348             if (texture2D) {
349               stateSet->setTextureAttribute(0, texture2D,
350                                             osg::StateAttribute::OVERRIDE);
351               stateSet->setTextureMode(0, GL_TEXTURE_2D,
352                                        osg::StateAttribute::ON);
353               _textureName = textureName;
354             }
355           }
356         }
357       }
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);
364         assert(alphaFunc);
365         alphaFunc->setReferenceValue(_thresholdProp->getFloatValue());
366       }
367       if (_materialProps.isAnimated() || !_prevState)
368           _materialProps.setMaterialValues(stateSet);
369       _prevState = true;
370     } else {
371       _prevState = false;
372     }
373     traverse(node, nv);
374   }
375 private:
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;
382   bool _prevState;
383 };
384 } // namespace
385
386
387 SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
388                                          SGPropertyNode* modelRoot,
389                                          const osgDB::ReaderWriter::Options*
390                                          options) :
391   SGAnimation(configNode, modelRoot),
392   texturePathList(options->getDatabasePathList())
393 {
394   if (configNode->hasChild("global"))
395     SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
396            "no longer supported");
397 }
398
399 osg::Group*
400 SGMaterialAnimation::createAnimationGroup(osg::Group& parent)
401 {
402   osg::Group* group = new osg::Group;
403   group->setName("material animation group");
404
405   SGPropertyNode* inputRoot = getModelRoot();
406   const SGPropertyNode* node = getConfig()->getChild("property-base");
407   if (node)
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);
416       if (texture2D) {
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);
423         }
424       }
425     }
426   }
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);
434   }
435
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();
454
455     } else {
456       mat = new osg::Material;
457       mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
458     }
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;
465       break;
466     case osg::Material::AMBIENT:
467       defaultColorModeMask = AMBIENT;
468       break;
469     case osg::Material::DIFFUSE:
470       defaultColorModeMask = DIFFUSE;
471       break;
472     case osg::Material::AMBIENT_AND_DIFFUSE:
473       defaultColorModeMask = AMBIENT | DIFFUSE;
474       break;
475     case osg::Material::SPECULAR:
476       defaultColorModeMask = SPECULAR;
477       break;
478     case osg::Material::EMISSION:
479       defaultColorModeMask = EMISSION;
480       break;
481     }
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);
490     }
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)
503                                      & AMBIENT_DIFFUSE);
504         if ((matColorModeMask & DIFFUSE) != 0)
505           mat->setColorMode(osg::Material::DIFFUSE);
506         else if ((matColorModeMask & AMBIENT) != 0)
507           mat->setColorMode(osg::Material::AMBIENT);
508         else
509           mat->setColorMode(osg::Material::OFF);
510       } else {
511         // The animation overrides the glColor color.
512         mat->setColorMode(osg::Material::OFF);
513       }
514     } else {
515       // No overlap between the animation and color mode, so leave
516       // the color mode alone.
517     }
518     stateSet->setAttribute(mat,(osg::StateAttribute::ON
519                                 | osg::StateAttribute::OVERRIDE));
520   }
521   bool matAnimated = false;
522   if (mat) {
523     MaterialPropertyAdapter adapter(getConfig(), inputRoot);
524     adapter.setMaterialValues(stateSet);
525     matAnimated = adapter.isAnimated();
526   }
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,
531                                                 getCondition(),
532                                                 getConfig(), inputRoot));
533   } else {
534     stateSet->setDataVariance(osg::Object::STATIC);
535   }
536   parent.addChild(group);
537   return group;
538 }
539
540 void
541 SGMaterialAnimation::install(osg::Node& node)
542 {
543   SGAnimation::install(node);
544
545     MaterialDefaultsVisitor defaultsVisitor;
546     node.accept(defaultsVisitor);
547     if (defaultsVisitor.material.valid()) {
548       defaultMaterial
549         = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
550     }
551     defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;
552 }