]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/SGMaterialAnimation.cxx
Improved tile cache priority scheme.
[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 #include <simgear/scene/model/ConditionNode.hxx>
31
32 using namespace std;
33 using namespace simgear;
34
35 namespace {
36 /**
37  * Get a color from properties.
38  */
39 struct ColorSpec {
40   float red, green, blue;
41   float factor;
42   float offset;
43   SGPropertyNode_ptr red_prop;
44   SGPropertyNode_ptr green_prop;
45   SGPropertyNode_ptr blue_prop;
46   SGPropertyNode_ptr factor_prop;
47   SGPropertyNode_ptr offset_prop;
48   SGVec4f v;
49   
50   ColorSpec(const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
51   {
52     red = -1.0;
53     green = -1.0;
54     blue = -1.0;
55     if (!configNode)
56       return;
57     
58     red = configNode->getFloatValue("red", -1.0);
59     green = configNode->getFloatValue("green", -1.0);
60     blue = configNode->getFloatValue("blue", -1.0);
61     factor = configNode->getFloatValue("factor", 1.0);
62     offset = configNode->getFloatValue("offset", 0.0);
63     
64     if (!modelRoot)
65       return;
66     const SGPropertyNode *node;
67     node = configNode->getChild("red-prop");
68     if (node)
69       red_prop = modelRoot->getNode(node->getStringValue(), true);
70     node = configNode->getChild("green-prop");
71     if (node)
72       green_prop = modelRoot->getNode(node->getStringValue(), true);
73     node = configNode->getChild("blue-prop");
74     if (node)
75       blue_prop = modelRoot->getNode(node->getStringValue(), true);
76     node = configNode->getChild("factor-prop");
77     if (node)
78       factor_prop = modelRoot->getNode(node->getStringValue(), true);
79     node = configNode->getChild("offset-prop");
80     if (node)
81       offset_prop = modelRoot->getNode(node->getStringValue(), true);
82   }
83   
84   bool dirty() {
85     return red >= 0 || green >= 0 || blue >= 0;
86   }
87   bool live() {
88     return red_prop || green_prop || blue_prop
89       || factor_prop || offset_prop;
90   }
91   SGVec4f &rgba() {
92     if (red_prop)
93       red = red_prop->getFloatValue();
94     if (green_prop)
95       green = green_prop->getFloatValue();
96     if (blue_prop)
97       blue = blue_prop->getFloatValue();
98     if (factor_prop)
99       factor = factor_prop->getFloatValue();
100     if (offset_prop)
101       offset = offset_prop->getFloatValue();
102     v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
103     v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
104     v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
105     v[3] = 1;
106     return v;
107   }
108
109   osg::Vec4 rgbaVec4()
110   {
111     return toOsg(rgba());
112   }
113   
114   SGVec4f &initialRgba() {
115     v[0] = SGMiscf::clip(red*factor + offset, 0, 1);
116     v[1] = SGMiscf::clip(green*factor + offset, 0, 1);
117     v[2] = SGMiscf::clip(blue*factor + offset, 0, 1);
118     v[3] = 1;
119     return v;
120   }
121 };
122
123 /**
124  * Get a property value from a property.
125  */
126 struct PropSpec {
127   float value;
128   float factor;
129   float offset;
130   float min;
131   float max;
132   SGPropertyNode_ptr value_prop;
133   SGPropertyNode_ptr factor_prop;
134   SGPropertyNode_ptr offset_prop;
135   
136   PropSpec(const char* valueName, const char* valuePropName,
137            const SGPropertyNode* configNode, SGPropertyNode* modelRoot)
138   {
139     value = -1;
140     if (!configNode)
141       return;
142     
143     value = configNode->getFloatValue(valueName, -1);
144     factor = configNode->getFloatValue("factor", 1);
145     offset = configNode->getFloatValue("offset", 0);
146     min = configNode->getFloatValue("min", 0);
147     max = configNode->getFloatValue("max", 1);
148     
149     if (!modelRoot)
150       return;
151     const SGPropertyNode *node;
152     node = configNode->getChild(valuePropName);
153     if (node)
154       value_prop = modelRoot->getNode(node->getStringValue(), true);
155     node = configNode->getChild("factor-prop");
156     if (node)
157       factor_prop = modelRoot->getNode(node->getStringValue(), true);
158     node = configNode->getChild("offset-prop");
159     if (node)
160       offset_prop = modelRoot->getNode(node->getStringValue(), true);
161   }
162   bool dirty() { return value >= 0.0; }
163   bool live() { return value_prop || factor_prop || offset_prop; }
164   float getValue()
165   {
166     if (value_prop)
167       value = value_prop->getFloatValue();
168     if (offset_prop)
169       offset = offset_prop->getFloatValue();
170     if (factor_prop)
171       factor = factor_prop->getFloatValue();
172     return SGMiscf::clip(value*factor + offset, min, max);
173   }
174   float getInitialValue()
175   {
176     return SGMiscf::clip(value*factor + offset, min, max);
177   }
178 };
179
180 /**
181  * The possible color properties supplied by a material animation.
182  */
183 enum SuppliedColor {
184   DIFFUSE = 1,
185   AMBIENT = 2,
186   SPECULAR = 4,
187   EMISSION = 8,
188   SHININESS = 16,
189   TRANSPARENCY = 32
190 };
191
192 const unsigned AMBIENT_DIFFUSE = AMBIENT | DIFFUSE;
193
194 const int allMaterialColors = (DIFFUSE | AMBIENT | SPECULAR | EMISSION
195                                | SHININESS);
196
197 // Visitor for finding default material colors in the animation node's
198 // subgraph. This makes some assumptions about the subgraph i.e.,
199 // there will be one material and one color value found. This is
200 // probably true for ac3d models and most uses of material animations,
201 // but will break down if, for example, you animate the transparency
202 // of a vertex colored model.
203 class MaterialDefaultsVisitor : public osg::NodeVisitor {
204 public:
205   MaterialDefaultsVisitor()
206     : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
207       ambientDiffuse(-1.0f, -1.0f, -1.0f, -1.0f)
208   {
209     setVisitorType(osg::NodeVisitor::NODE_VISITOR);
210   }
211
212   virtual void apply(osg::Node& node)
213   {
214     maybeGetMaterialValues(node.getStateSet());
215     traverse(node);
216   }
217
218   virtual void apply(osg::Geode& node)
219   {
220     using namespace simgear;
221     EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
222     if (eg) {
223       const Effect* effect = eg->getEffect();
224       if (effect)
225         for (vector<osg::ref_ptr<Technique> >::const_iterator itr
226                = effect->techniques.begin(), end = effect->techniques.end();
227              itr != end;
228              ++itr) {
229           const Technique* tniq = itr->get();
230           for (vector<osg::ref_ptr<Pass> >::const_iterator pitr
231                  = tniq->passes.begin(), pend = tniq->passes.end();
232                pitr != pend;
233                ++pitr)
234             maybeGetMaterialValues(pitr->get());
235         }
236     } else {
237       maybeGetMaterialValues(node.getStateSet());
238     }
239     int numDrawables = node.getNumDrawables();
240     for (int i = 0; i < numDrawables; i++) {
241       osg::Geometry* geom = dynamic_cast<osg::Geometry*>(node.getDrawable(i));
242       if (!geom || geom->getColorBinding() != osg::Geometry::BIND_OVERALL)
243         continue;
244       maybeGetMaterialValues(geom->getStateSet());
245       osg::Array* colorArray = geom->getColorArray();
246       osg::Vec4Array* colorVec4 = dynamic_cast<osg::Vec4Array*>(colorArray);
247       if (colorVec4) {
248         ambientDiffuse = (*colorVec4)[0];
249         break;
250       }
251       osg::Vec3Array* colorVec3 = dynamic_cast<osg::Vec3Array*>(colorArray);
252       if (colorVec3) {
253         ambientDiffuse = osg::Vec4((*colorVec3)[0], 1.0f);
254         break;
255       }
256     }
257   }
258   
259   void maybeGetMaterialValues(const osg::StateSet* stateSet)
260   {
261     if (!stateSet)
262       return;
263     const osg::Material* nodeMat
264       = dynamic_cast<const osg::Material*>(stateSet
265                                            ->getAttribute(osg::StateAttribute
266                                                           ::MATERIAL));
267     if (!nodeMat)
268       return;
269     material = nodeMat;
270   }
271
272   osg::ref_ptr<const osg::Material> material;
273   osg::Vec4 ambientDiffuse;
274 };
275
276 class MaterialPropertyAdapter
277 {
278 public:
279     MaterialPropertyAdapter(const SGPropertyNode* configNode,
280                             SGPropertyNode* modelRoot) :
281         _ambient(configNode->getChild("ambient"), modelRoot),
282         _diffuse(configNode->getChild("diffuse"), modelRoot),
283         _specular(configNode->getChild("specular"), modelRoot),
284         _emission(configNode->getChild("emission"), modelRoot),
285         _shininess("shininess", "shininess-prop",
286                    configNode/*->getChild("shininess")*/, modelRoot),
287         _transparency("alpha", "alpha-prop",
288                       configNode->getChild("transparency"), modelRoot)
289     {
290         _shininess.max = 128;
291         _isAnimated = (_ambient.live() || _diffuse.live() || _specular.live()
292                        || _emission.live() || _shininess.live()
293                        || _transparency.live());
294     }
295     bool isAnimated() { return _isAnimated; }
296     // This takes a StateSet argument because the rendering bin will
297     // be changed if there is transparency.
298     void setMaterialValues(osg::StateSet* stateSet)
299     {
300         osg::StateAttribute* stateAttribute
301             = stateSet->getAttribute(osg::StateAttribute::MATERIAL);
302         osg::Material* material = dynamic_cast<osg::Material*>(stateAttribute);
303         if (material) {
304             if (_ambient.live() || _ambient.dirty())
305                 material->setAmbient(osg::Material::FRONT_AND_BACK,
306                                      _ambient.rgbaVec4());      
307             if (_diffuse.live() || _diffuse.dirty())
308                 material->setDiffuse(osg::Material::FRONT_AND_BACK,
309                                      _diffuse.rgbaVec4());
310             if (_specular.live() || _specular.dirty())
311                 material->setSpecular(osg::Material::FRONT_AND_BACK,
312                                       _specular.rgbaVec4());
313             if (_emission.live() || _emission.dirty())
314                 material->setEmission(osg::Material::FRONT_AND_BACK,
315                                       _emission.rgbaVec4());
316             if (_shininess.live() || _shininess.dirty())
317                 material->setShininess(osg::Material::FRONT_AND_BACK,
318                                        _shininess.getValue());
319             if (_transparency.live() || _transparency.dirty()) {
320                 float alpha = _transparency.getValue();
321                 material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
322                 if (alpha < 1.0f) {
323                     stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
324                     stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
325                 } else {
326                     stateSet->setRenderingHint(osg::StateSet::DEFAULT_BIN);
327                 }
328             }
329         }
330     }
331     ColorSpec _ambient;
332     ColorSpec _diffuse;
333     ColorSpec _specular;
334     ColorSpec _emission;
335     PropSpec _shininess;
336     PropSpec _transparency;
337     bool _isAnimated;
338
339 };
340
341 class UpdateCallback : public osg::NodeCallback {
342 public:
343   UpdateCallback(const osgDB::FilePathList& texturePathList,
344                  const SGPropertyNode* configNode, SGPropertyNode* modelRoot) :
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 (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 SGPropertyNode> _textureProp;
403   SGSharedPtr<const SGPropertyNode> _thresholdProp;
404   std::string _textureName;
405   MaterialPropertyAdapter _materialProps;
406   osgDB::FilePathList _texturePathList;
407   bool _prevState;
408 };
409 } // namespace
410
411
412 SGMaterialAnimation::SGMaterialAnimation(const SGPropertyNode* configNode,
413                                          SGPropertyNode* modelRoot,
414                                          const osgDB::ReaderWriter::Options*
415                                          options) :
416   SGAnimation(configNode, modelRoot),
417   texturePathList(options->getDatabasePathList())
418 {
419   if (configNode->hasChild("global"))
420     SG_LOG(SG_IO, SG_ALERT, "Use of <global> in material animation is "
421            "no longer supported");
422 }
423
424 osg::Group*
425 SGMaterialAnimation::createAnimationGroup(osg::Group& parent)
426 {
427   osg::Group* group = new osg::Group;
428   group->setName("material animation group");
429
430   SGPropertyNode* inputRoot = getModelRoot();
431   const SGPropertyNode* node = getConfig()->getChild("property-base");
432   if (node)
433     inputRoot = getModelRoot()->getNode(node->getStringValue(), true);
434   osg::StateSet* stateSet = group->getOrCreateStateSet();  
435   if (getConfig()->hasChild("texture")) {
436     std::string textureName = getConfig()->getStringValue("texture");
437     std::string textureFile;
438     textureFile = osgDB::findFileInPath(textureName, texturePathList);
439     if (!textureFile.empty()) {
440       osg::Texture2D* texture2D = SGLoadTexture2D(textureFile);
441       if (texture2D) {
442         stateSet->setTextureAttribute(0, texture2D,
443                                       osg::StateAttribute::OVERRIDE);
444         stateSet->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON);
445         if (texture2D->getImage()->isImageTranslucent()) {
446           stateSet->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
447           stateSet->setMode(GL_BLEND, osg::StateAttribute::ON);
448         }
449       }
450     }
451   }
452   if (getConfig()->hasChild("threshold-prop") ||
453       getConfig()->hasChild("threshold")) {
454     osg::AlphaFunc* alphaFunc = new osg::AlphaFunc;
455     alphaFunc->setFunction(osg::AlphaFunc::GREATER);
456     float threshold = getConfig()->getFloatValue("threshold", 0);
457     alphaFunc->setReferenceValue(threshold);
458     stateSet->setAttribute(alphaFunc, osg::StateAttribute::OVERRIDE);
459   }
460
461   unsigned suppliedColors = 0;
462   if (getConfig()->hasChild("ambient"))
463     suppliedColors |= AMBIENT;
464   if (getConfig()->hasChild("diffuse"))
465     suppliedColors |= DIFFUSE;
466   if (getConfig()->hasChild("specular"))
467     suppliedColors |= SPECULAR;
468   if (getConfig()->hasChild("emission"))
469     suppliedColors |= EMISSION;
470   if (getConfig()->hasChild("shininess")
471       || getConfig()->hasChild("shininess-prop"))
472     suppliedColors |= SHININESS;
473   if (getConfig()->hasChild("transparency"))
474     suppliedColors |= TRANSPARENCY;
475   osg::Material* mat = 0;
476   if (suppliedColors != 0) {
477     if (defaultMaterial.valid()) {
478       mat = defaultMaterial.get();
479
480     } else {
481       mat = new osg::Material;
482       mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
483     }
484     mat->setDataVariance(osg::Object::DYNAMIC);
485     unsigned defaultColorModeMask = 0;
486     mat->setUpdateCallback(0); // Just to make sure.
487     // XXX This should probably go away, as ac3d models always have a
488     // DIFFUSE color mode.
489     switch (mat->getColorMode()) {
490     case osg::Material::OFF:
491       defaultColorModeMask = 0;
492       break;
493     case osg::Material::AMBIENT:
494       defaultColorModeMask = AMBIENT;
495       break;
496     case osg::Material::DIFFUSE:
497       defaultColorModeMask = DIFFUSE;
498       break;
499     case osg::Material::AMBIENT_AND_DIFFUSE:
500       defaultColorModeMask = AMBIENT | DIFFUSE;
501       break;
502     case osg::Material::SPECULAR:
503       defaultColorModeMask = SPECULAR;
504       break;
505     case osg::Material::EMISSION:
506       defaultColorModeMask = EMISSION;
507       break;
508     }
509     // Copy the color found by traversing geometry into the material
510     // in case we need to specify it (e.g., transparency) and it is
511     // not specified by the animation.
512     if (defaultAmbientDiffuse.x() >= 0) {
513       if (defaultColorModeMask & AMBIENT)
514         mat->setAmbient(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
515       if (defaultColorModeMask & DIFFUSE)
516         mat->setDiffuse(osg::Material::FRONT_AND_BACK, defaultAmbientDiffuse);
517     }
518     // Compute which colors in the animation override colors set via
519     // colorMode / glColor, and set the colorMode for the animation's
520     // material accordingly. 
521     if (suppliedColors & TRANSPARENCY) {
522       // All colors will be affected by the material. Hope all the
523       // defaults are fine, if needed.
524       mat->setColorMode(osg::Material::OFF);
525     } else if ((suppliedColors & defaultColorModeMask) != 0) {
526       // First deal with the complicated AMBIENT/DIFFUSE case.
527       if ((defaultColorModeMask & AMBIENT_DIFFUSE) != 0) {
528         // glColor can supply colors not specified by the animation.
529         unsigned matColorModeMask = ((~suppliedColors & defaultColorModeMask)
530                                      & AMBIENT_DIFFUSE);
531         if ((matColorModeMask & DIFFUSE) != 0)
532           mat->setColorMode(osg::Material::DIFFUSE);
533         else if ((matColorModeMask & AMBIENT) != 0)
534           mat->setColorMode(osg::Material::AMBIENT);
535         else
536           mat->setColorMode(osg::Material::OFF);
537       } else {
538         // The animation overrides the glColor color.
539         mat->setColorMode(osg::Material::OFF);
540       }
541     } else {
542       // No overlap between the animation and color mode, so leave
543       // the color mode alone.
544     }
545     stateSet->setAttribute(mat,(osg::StateAttribute::ON
546                                 | osg::StateAttribute::OVERRIDE));
547   }
548   bool matAnimated = false;
549   if (mat) {
550     MaterialPropertyAdapter adapter(getConfig(), inputRoot);
551     adapter.setMaterialValues(stateSet);
552     matAnimated = adapter.isAnimated();
553   }
554   if (matAnimated || getConfig()->hasChild("texture-prop")
555       || getConfig()->hasChild("threshold-prop") || getCondition()) {
556     stateSet->setDataVariance(osg::Object::DYNAMIC);
557     group->setUpdateCallback(new UpdateCallback(texturePathList,
558                                                 getConfig(), inputRoot));
559   } else {
560     stateSet->setDataVariance(osg::Object::STATIC);
561   }
562   if (getCondition()) {
563     ConditionNode* cn = new ConditionNode;
564     cn->setCondition(getCondition());
565     osg::Group* modelGroup = new osg::Group;
566     group->addChild(modelGroup);
567     cn->addChild(group);
568     cn->addChild(modelGroup);
569     parent.addChild(cn);
570     return modelGroup;
571   } else {
572     parent.addChild(group);
573     return group;
574   }
575 }
576
577 void
578 SGMaterialAnimation::install(osg::Node& node)
579 {
580   SGAnimation::install(node);
581
582     MaterialDefaultsVisitor defaultsVisitor;
583     node.accept(defaultsVisitor);
584     if (defaultsVisitor.material.valid()) {
585       defaultMaterial
586         = static_cast<osg::Material*>(defaultsVisitor.material->clone(osg::CopyOp::SHALLOW_COPY));
587     }
588     defaultAmbientDiffuse = defaultsVisitor.ambientDiffuse;
589 }
590
591 const char* colorNames[] =
592 {
593     "ambient",
594     "diffuse",
595     "specular",
596     "emission"
597 };
598
599 // Build an effect which mimics the material color mode in a
600 // shader. The OpenGL material values will be overridden by the
601 // material animation's material.
602 //
603 // This is a hack to get the effect to respect the values set in the
604 // material, set up by the animation, which overrides the values in
605 // the effect's material attributes. Things will be different when
606 // material animations are implemented purely by manipulating effects.
607
608 SGPropertyNode_ptr
609 SGMaterialAnimation::makeEffectProperties(const SGPropertyNode* animProp)
610 {
611     SGPropertyNode_ptr eRoot = new SGPropertyNode;
612     SGPropertyNode* inherit = makeNode(eRoot, "inherits-from");
613     if (animProp->hasChild("diffuse") || animProp->hasChild("transparency"))
614         inherit->setStringValue("Effects/material-off");
615     else
616         inherit->setStringValue("Effects/material-diffuse");
617     return eRoot;
618 }