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