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