]> git.mxchange.org Git - simgear.git/blob - simgear/scene/material/Effect.cxx
3ad9316216fd0904e039f9c8caeb79f76092ab2c
[simgear.git] / simgear / scene / material / Effect.cxx
1 // Copyright (C) 2008 - 2009  Tim Moore timoore@redhat.com
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16
17 #ifdef HAVE_CONFIG_H
18 #  include <simgear_config.h>
19 #endif
20
21 #include "Effect.hxx"
22 #include "EffectBuilder.hxx"
23 #include "EffectGeode.hxx"
24 #include "Technique.hxx"
25 #include "Pass.hxx"
26 #include "TextureBuilder.hxx"
27
28 #include <algorithm>
29 #include <functional>
30 #include <iterator>
31 #include <map>
32 #include <utility>
33 #include <boost/tr1/unordered_map.hpp>
34
35 #include <boost/bind.hpp>
36 #include <boost/foreach.hpp>
37 #include <boost/functional/hash.hpp>
38 #include <boost/tuple/tuple.hpp>
39 #include <boost/tuple/tuple_comparison.hpp>
40
41 #include <osg/AlphaFunc>
42 #include <osg/BlendFunc>
43 #include <osg/CullFace>
44 #include <osg/Depth>
45 #include <osg/Drawable>
46 #include <osg/Material>
47 #include <osg/Math>
48 #include <osg/PolygonMode>
49 #include <osg/Program>
50 #include <osg/Referenced>
51 #include <osg/RenderInfo>
52 #include <osg/ShadeModel>
53 #include <osg/StateSet>
54 #include <osg/Stencil>
55 #include <osg/TexEnv>
56 #include <osg/Texture1D>
57 #include <osg/Texture2D>
58 #include <osg/Texture3D>
59 #include <osg/TextureRectangle>
60 #include <osg/Uniform>
61 #include <osg/Vec4d>
62 #include <osgUtil/CullVisitor>
63 #include <osgDB/FileUtils>
64 #include <osgDB/Input>
65 #include <osgDB/ParameterOutput>
66 #include <osgDB/ReadFile>
67 #include <osgDB/Registry>
68
69 #include <simgear/scene/model/SGReaderWriterXMLOptions.hxx>
70 #include <simgear/scene/tgdb/userdata.hxx>
71 #include <simgear/scene/util/SGSceneFeatures.hxx>
72 #include <simgear/scene/util/StateAttributeFactory.hxx>
73 #include <simgear/structure/OSGUtils.hxx>
74 #include <simgear/structure/SGExpression.hxx>
75
76
77
78 namespace simgear
79 {
80 using namespace std;
81 using namespace osg;
82 using namespace osgUtil;
83
84 using namespace effect;
85
86 Effect::Effect()
87     : _cache(0), _isRealized(false)
88 {
89 }
90
91 Effect::Effect(const Effect& rhs, const CopyOp& copyop)
92     : root(rhs.root), parametersProp(rhs.parametersProp), _cache(0),
93       _isRealized(rhs._isRealized)
94 {
95     typedef vector<ref_ptr<Technique> > TechniqueList;
96     for (TechniqueList::const_iterator itr = rhs.techniques.begin(),
97              end = rhs.techniques.end();
98          itr != end;
99          ++itr)
100         techniques.push_back(static_cast<Technique*>(copyop(itr->get())));
101 }
102
103 // Assume that the last technique is always valid.
104 StateSet* Effect::getDefaultStateSet()
105 {
106     Technique* tniq = techniques.back().get();
107     if (!tniq)
108         return 0;
109     Pass* pass = tniq->passes.front().get();
110     return pass;
111 }
112
113 // There should always be a valid technique in an effect.
114
115 Technique* Effect::chooseTechnique(RenderInfo* info)
116 {
117     BOOST_FOREACH(ref_ptr<Technique>& technique, techniques)
118     {
119         if (technique->valid(info) == Technique::VALID)
120             return technique.get();
121     }
122     return 0;
123 }
124
125 void Effect::resizeGLObjectBuffers(unsigned int maxSize)
126 {
127     BOOST_FOREACH(const ref_ptr<Technique>& technique, techniques)
128     {
129         technique->resizeGLObjectBuffers(maxSize);
130     }
131 }
132
133 void Effect::releaseGLObjects(osg::State* state) const
134 {
135     BOOST_FOREACH(const ref_ptr<Technique>& technique, techniques)
136     {
137         technique->releaseGLObjects(state);
138     }
139 }
140
141 Effect::~Effect()
142 {
143     delete _cache;
144 }
145
146 void buildPass(Effect* effect, Technique* tniq, const SGPropertyNode* prop,
147                const SGReaderWriterXMLOptions* options)
148 {
149     Pass* pass = new Pass;
150     tniq->passes.push_back(pass);
151     for (int i = 0; i < prop->nChildren(); ++i) {
152         const SGPropertyNode* attrProp = prop->getChild(i);
153         PassAttributeBuilder* builder
154             = PassAttributeBuilder::find(attrProp->getNameString());
155         if (builder)
156             builder->buildAttribute(effect, pass, attrProp, options);
157         else
158             SG_LOG(SG_INPUT, SG_ALERT,
159                    "skipping unknown pass attribute " << attrProp->getName());
160     }
161 }
162
163 osg::Vec4f getColor(const SGPropertyNode* prop)
164 {
165     if (prop->nChildren() == 0) {
166         if (prop->getType() == props::VEC4D) {
167             return osg::Vec4f(toOsg(prop->getValue<SGVec4d>()));
168         } else if (prop->getType() == props::VEC3D) {
169             return osg::Vec4f(toOsg(prop->getValue<SGVec3d>()), 1.0f);
170         } else {
171             SG_LOG(SG_INPUT, SG_ALERT,
172                    "invalid color property " << prop->getName() << " "
173                    << prop->getStringValue());
174             return osg::Vec4f(0.0f, 0.0f, 0.0f, 1.0f);
175         }
176     } else {
177         osg::Vec4f result;
178         static const char* colors[] = {"r", "g", "b"};
179         for (int i = 0; i < 3; ++i) {
180             const SGPropertyNode* componentProp = prop->getChild(colors[i]);
181             result[i] = componentProp ? componentProp->getValue<float>() : 0.0f;
182         }
183         const SGPropertyNode* alphaProp = prop->getChild("a");
184         result[3] = alphaProp ? alphaProp->getValue<float>() : 1.0f;
185         return result;
186     }
187 }
188
189 struct LightingBuilder : public PassAttributeBuilder
190 {
191     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
192                         const SGReaderWriterXMLOptions* options);
193 };
194
195 void LightingBuilder::buildAttribute(Effect* effect, Pass* pass,
196                                      const SGPropertyNode* prop,
197                                      const SGReaderWriterXMLOptions* options)
198 {
199     const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
200     if (!realProp)
201         return;
202     pass->setMode(GL_LIGHTING, (realProp->getValue<bool>() ? StateAttribute::ON
203                                 : StateAttribute::OFF));
204 }
205
206 InstallAttributeBuilder<LightingBuilder> installLighting("lighting");
207
208 struct ShadeModelBuilder : public PassAttributeBuilder
209 {
210     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
211                         const SGReaderWriterXMLOptions* options)
212     {
213         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
214         if (!realProp)
215             return;
216         StateAttributeFactory *attrFact = StateAttributeFactory::instance();
217         string propVal = realProp->getStringValue();
218         if (propVal == "flat")
219             pass->setAttribute(attrFact->getFlatShadeModel());
220         else if (propVal == "smooth")
221             pass->setAttribute(attrFact->getSmoothShadeModel());
222         else
223             SG_LOG(SG_INPUT, SG_ALERT,
224                    "invalid shade model property " << propVal);
225     }
226 };
227
228 InstallAttributeBuilder<ShadeModelBuilder> installShadeModel("shade-model");
229
230 struct CullFaceBuilder : PassAttributeBuilder
231 {
232     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
233                         const SGReaderWriterXMLOptions* options)
234     {
235         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
236         if (!realProp) {
237             pass->setMode(GL_CULL_FACE, StateAttribute::OFF);
238             return;
239         }
240         StateAttributeFactory *attrFact = StateAttributeFactory::instance();
241         string propVal = realProp->getStringValue();
242         if (propVal == "front")
243             pass->setAttributeAndModes(attrFact->getCullFaceFront());
244         else if (propVal == "back")
245             pass->setAttributeAndModes(attrFact->getCullFaceBack());
246         else if (propVal == "front-back")
247             pass->setAttributeAndModes(new CullFace(CullFace::FRONT_AND_BACK));
248         else if (propVal == "off")
249             pass->setMode(GL_CULL_FACE, StateAttribute::OFF);
250         else
251             SG_LOG(SG_INPUT, SG_ALERT,
252                    "invalid cull face property " << propVal);            
253     }    
254 };
255
256 InstallAttributeBuilder<CullFaceBuilder> installCullFace("cull-face");
257
258 struct ColorMaskBuilder : PassAttributeBuilder
259 {
260     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
261                         const SGReaderWriterXMLOptions* options)
262     {
263         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
264         if (!realProp)
265             return;
266
267         ColorMask *mask = new ColorMask;
268         Vec4 m = getColor(realProp);
269         mask->setMask(m.r(), m.g(), m.b(), m.a());
270         pass->setAttributeAndModes(mask);
271     }    
272 };
273
274 InstallAttributeBuilder<ColorMaskBuilder> installColorMask("color-mask");
275
276 EffectNameValue<StateSet::RenderingHint> renderingHintInit[] =
277 {
278     { "default", StateSet::DEFAULT_BIN },
279     { "opaque", StateSet::OPAQUE_BIN },
280     { "transparent", StateSet::TRANSPARENT_BIN }
281 };
282
283 EffectPropertyMap<StateSet::RenderingHint> renderingHints(renderingHintInit);
284
285 struct HintBuilder : public PassAttributeBuilder
286 {
287     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
288                         const SGReaderWriterXMLOptions* options)
289     {
290         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
291         if (!realProp)
292             return;
293         StateSet::RenderingHint renderingHint = StateSet::DEFAULT_BIN;
294         findAttr(renderingHints, realProp, renderingHint);
295         pass->setRenderingHint(renderingHint);
296     }    
297 };
298
299 InstallAttributeBuilder<HintBuilder> installHint("rendering-hint");
300
301 struct RenderBinBuilder : public PassAttributeBuilder
302 {
303     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
304                         const SGReaderWriterXMLOptions* options)
305     {
306         if (!isAttributeActive(effect, prop))
307             return;
308         const SGPropertyNode* binProp = prop->getChild("bin-number");
309         binProp = getEffectPropertyNode(effect, binProp);
310         const SGPropertyNode* nameProp = prop->getChild("bin-name");
311         nameProp = getEffectPropertyNode(effect, nameProp);
312         if (binProp && nameProp) {
313             pass->setRenderBinDetails(binProp->getIntValue(),
314                                       nameProp->getStringValue());
315         } else {
316             if (!binProp)
317                 SG_LOG(SG_INPUT, SG_ALERT,
318                        "No render bin number specified in render bin section");
319             if (!nameProp)
320                 SG_LOG(SG_INPUT, SG_ALERT,
321                        "No render bin name specified in render bin section");
322         }
323     }
324 };
325
326 InstallAttributeBuilder<RenderBinBuilder> installRenderBin("render-bin");
327
328 struct MaterialBuilder : public PassAttributeBuilder
329 {
330     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
331                         const SGReaderWriterXMLOptions* options);
332 };
333
334 EffectNameValue<Material::ColorMode> colorModeInit[] =
335 {
336     { "ambient", Material::AMBIENT },
337     { "ambient-and-diffuse", Material::AMBIENT_AND_DIFFUSE },
338     { "diffuse", Material::DIFFUSE },
339     { "emissive", Material::EMISSION },
340     { "specular", Material::SPECULAR },
341     { "off", Material::OFF }
342 };
343 EffectPropertyMap<Material::ColorMode> colorModes(colorModeInit);
344
345 void MaterialBuilder::buildAttribute(Effect* effect, Pass* pass,
346                                      const SGPropertyNode* prop,
347                                      const SGReaderWriterXMLOptions* options)
348 {
349     if (!isAttributeActive(effect, prop))
350         return;
351     Material* mat = new Material;
352     const SGPropertyNode* color = 0;
353     if ((color = getEffectPropertyChild(effect, prop, "ambient")))
354         mat->setAmbient(Material::FRONT_AND_BACK, getColor(color));
355     if ((color = getEffectPropertyChild(effect, prop, "ambient-front")))
356         mat->setAmbient(Material::FRONT, getColor(color));
357     if ((color = getEffectPropertyChild(effect, prop, "ambient-back")))
358         mat->setAmbient(Material::BACK, getColor(color));
359     if ((color = getEffectPropertyChild(effect, prop, "diffuse")))
360         mat->setDiffuse(Material::FRONT_AND_BACK, getColor(color));
361     if ((color = getEffectPropertyChild(effect, prop, "diffuse-front")))
362         mat->setDiffuse(Material::FRONT, getColor(color));
363     if ((color = getEffectPropertyChild(effect, prop, "diffuse-back")))
364         mat->setDiffuse(Material::BACK, getColor(color));
365     if ((color = getEffectPropertyChild(effect, prop, "specular")))
366         mat->setSpecular(Material::FRONT_AND_BACK, getColor(color));
367     if ((color = getEffectPropertyChild(effect, prop, "specular-front")))
368         mat->setSpecular(Material::FRONT, getColor(color));
369     if ((color = getEffectPropertyChild(effect, prop, "specular-back")))
370         mat->setSpecular(Material::BACK, getColor(color));
371     if ((color = getEffectPropertyChild(effect, prop, "emissive")))
372         mat->setEmission(Material::FRONT_AND_BACK, getColor(color));
373     if ((color = getEffectPropertyChild(effect, prop, "emissive-front")))
374         mat->setEmission(Material::FRONT, getColor(color));        
375     if ((color = getEffectPropertyChild(effect, prop, "emissive-back")))
376         mat->setEmission(Material::BACK, getColor(color));        
377     const SGPropertyNode* shininess = 0;
378     mat->setShininess(Material::FRONT_AND_BACK, 0.0f);
379     if ((shininess = getEffectPropertyChild(effect, prop, "shininess")))
380         mat->setShininess(Material::FRONT_AND_BACK, shininess->getFloatValue());
381     if ((shininess = getEffectPropertyChild(effect, prop, "shininess-front")))
382         mat->setShininess(Material::FRONT, shininess->getFloatValue());
383     if ((shininess = getEffectPropertyChild(effect, prop, "shininess-back")))
384         mat->setShininess(Material::BACK, shininess->getFloatValue());
385     Material::ColorMode colorMode = Material::OFF;
386     findAttr(colorModes, getEffectPropertyChild(effect, prop, "color-mode"),
387              colorMode);
388     mat->setColorMode(colorMode);
389     pass->setAttribute(mat);
390 }
391
392 InstallAttributeBuilder<MaterialBuilder> installMaterial("material");
393
394 EffectNameValue<BlendFunc::BlendFuncMode> blendFuncModesInit[] =
395 {
396     {"dst-alpha", BlendFunc::DST_ALPHA},
397     {"dst-color", BlendFunc::DST_COLOR},
398     {"one", BlendFunc::ONE},
399     {"one-minus-dst-alpha", BlendFunc::ONE_MINUS_DST_ALPHA},
400     {"one-minus-dst-color", BlendFunc::ONE_MINUS_DST_COLOR},
401     {"one-minus-src-alpha", BlendFunc::ONE_MINUS_SRC_ALPHA},
402     {"one-minus-src-color", BlendFunc::ONE_MINUS_SRC_COLOR},
403     {"src-alpha", BlendFunc::SRC_ALPHA},
404     {"src-alpha-saturate", BlendFunc::SRC_ALPHA_SATURATE},
405     {"src-color", BlendFunc::SRC_COLOR},
406     {"constant-color", BlendFunc::CONSTANT_COLOR},
407     {"one-minus-constant-color", BlendFunc::ONE_MINUS_CONSTANT_COLOR},
408     {"constant-alpha", BlendFunc::CONSTANT_ALPHA},
409     {"one-minus-constant-alpha", BlendFunc::ONE_MINUS_CONSTANT_ALPHA},
410     {"zero", BlendFunc::ZERO}
411 };
412 EffectPropertyMap<BlendFunc::BlendFuncMode> blendFuncModes(blendFuncModesInit);
413
414 struct BlendBuilder : public PassAttributeBuilder
415 {
416     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
417                         const SGReaderWriterXMLOptions* options)
418     {
419         if (!isAttributeActive(effect, prop))
420             return;
421         // XXX Compatibility with early <blend> syntax; should go away
422         // before a release
423         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
424         if (!realProp)
425             return;
426         if (realProp->nChildren() == 0) {
427             pass->setMode(GL_BLEND, (realProp->getBoolValue()
428                                      ? StateAttribute::ON
429                                      : StateAttribute::OFF));
430             return;
431         }
432
433         const SGPropertyNode* pmode = getEffectPropertyChild(effect, prop,
434                                                              "mode");
435         // XXX When dynamic parameters are supported, this code should
436         // create the blend function even if the mode is off.
437         if (pmode && !pmode->getValue<bool>()) {
438             pass->setMode(GL_BLEND, StateAttribute::OFF);
439             return;
440         }
441         const SGPropertyNode* psource
442             = getEffectPropertyChild(effect, prop, "source");
443         const SGPropertyNode* pdestination
444             = getEffectPropertyChild(effect, prop, "destination");
445         const SGPropertyNode* psourceRGB
446             = getEffectPropertyChild(effect, prop, "source-rgb");
447         const SGPropertyNode* psourceAlpha
448             = getEffectPropertyChild(effect, prop, "source-alpha");
449         const SGPropertyNode* pdestRGB
450             = getEffectPropertyChild(effect, prop, "destination-rgb");
451         const SGPropertyNode* pdestAlpha
452             = getEffectPropertyChild(effect, prop, "destination-alpha");
453         BlendFunc::BlendFuncMode sourceMode = BlendFunc::ONE;
454         BlendFunc::BlendFuncMode destMode = BlendFunc::ZERO;
455         if (psource)
456             findAttr(blendFuncModes, psource, sourceMode);
457         if (pdestination)
458             findAttr(blendFuncModes, pdestination, destMode);
459         if (psource && pdestination
460             && !(psourceRGB || psourceAlpha || pdestRGB || pdestAlpha)
461             && sourceMode == BlendFunc::SRC_ALPHA
462             && destMode == BlendFunc::ONE_MINUS_SRC_ALPHA) {
463             pass->setAttributeAndModes(StateAttributeFactory::instance()
464                                        ->getStandardBlendFunc());
465             return;
466         }
467         BlendFunc* blendFunc = new BlendFunc;
468         if (psource)
469             blendFunc->setSource(sourceMode);
470         if (pdestination)
471             blendFunc->setDestination(destMode);
472         if (psourceRGB) {
473             BlendFunc::BlendFuncMode sourceRGBMode;
474             findAttr(blendFuncModes, psourceRGB, sourceRGBMode);
475             blendFunc->setSourceRGB(sourceRGBMode);
476         }
477         if (pdestRGB) {
478             BlendFunc::BlendFuncMode destRGBMode;
479             findAttr(blendFuncModes, pdestRGB, destRGBMode);
480             blendFunc->setDestinationRGB(destRGBMode);
481         }
482         if (psourceAlpha) {
483             BlendFunc::BlendFuncMode sourceAlphaMode;
484             findAttr(blendFuncModes, psourceAlpha, sourceAlphaMode);
485             blendFunc->setSourceAlpha(sourceAlphaMode);
486         }
487         if (pdestAlpha) {
488             BlendFunc::BlendFuncMode destAlphaMode;
489             findAttr(blendFuncModes, pdestAlpha, destAlphaMode);
490             blendFunc->setDestinationAlpha(destAlphaMode);
491         }
492         pass->setAttributeAndModes(blendFunc);
493     }
494 };
495
496 InstallAttributeBuilder<BlendBuilder> installBlend("blend");
497
498
499 EffectNameValue<Stencil::Function> stencilFunctionInit[] =
500 {
501     {"never", Stencil::NEVER },
502     {"less", Stencil::LESS},
503     {"equal", Stencil::EQUAL},
504     {"less-or-equal", Stencil::LEQUAL},
505     {"greater", Stencil::GREATER},
506     {"not-equal", Stencil::NOTEQUAL},
507     {"greater-or-equal", Stencil::GEQUAL},
508     {"always", Stencil::ALWAYS}
509 };
510
511 EffectPropertyMap<Stencil::Function> stencilFunction(stencilFunctionInit);
512
513 EffectNameValue<Stencil::Operation> stencilOperationInit[] =
514 {
515     {"keep", Stencil::KEEP},
516     {"zero", Stencil::ZERO},
517     {"replace", Stencil::REPLACE},
518     {"increase", Stencil::INCR},
519     {"decrease", Stencil::DECR},
520     {"invert", Stencil::INVERT},
521     {"increase-wrap", Stencil::INCR_WRAP},
522     {"decrease-wrap", Stencil::DECR_WRAP}
523 };
524
525 EffectPropertyMap<Stencil::Operation> stencilOperation(stencilOperationInit);
526
527 struct StencilBuilder : public PassAttributeBuilder
528 {
529     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
530                         const SGReaderWriterXMLOptions* options)
531     {
532         if (!isAttributeActive(effect, prop))
533             return;
534
535         const SGPropertyNode* pmode = getEffectPropertyChild(effect, prop,
536                                                              "mode");
537         if (pmode && !pmode->getValue<bool>()) {
538             pass->setMode(GL_STENCIL, StateAttribute::OFF);
539             return;
540         }
541         const SGPropertyNode* pfunction
542             = getEffectPropertyChild(effect, prop, "function");
543         const SGPropertyNode* pvalue
544             = getEffectPropertyChild(effect, prop, "value");
545         const SGPropertyNode* pmask
546             = getEffectPropertyChild(effect, prop, "mask");
547         const SGPropertyNode* psfail
548             = getEffectPropertyChild(effect, prop, "stencil-fail");
549         const SGPropertyNode* pzfail
550             = getEffectPropertyChild(effect, prop, "z-fail");
551         const SGPropertyNode* ppass
552             = getEffectPropertyChild(effect, prop, "pass");
553
554         Stencil::Function func = Stencil::ALWAYS;  // Always pass
555         int ref = 0;
556         unsigned int mask = ~0u;  // All bits on
557         Stencil::Operation sfailop = Stencil::KEEP;  // Keep the old values as default
558         Stencil::Operation zfailop = Stencil::KEEP;
559         Stencil::Operation passop = Stencil::KEEP;
560
561         ref_ptr<Stencil> stencilFunc = new Stencil;
562
563         if (pfunction)
564             findAttr(stencilFunction, pfunction, func);
565         if (pvalue)
566             ref = pvalue->getIntValue();
567         if (pmask) 
568             mask = pmask->getIntValue();
569
570         if (psfail)
571             findAttr(stencilOperation, psfail, sfailop);
572         if (pzfail)
573             findAttr(stencilOperation, pzfail, zfailop);
574         if (ppass)
575             findAttr(stencilOperation, ppass, passop);
576
577         // Set the stencil operation
578         stencilFunc->setFunction(func, ref, mask);
579
580         // Set the operation, s-fail, s-pass/z-fail, s-pass/z-pass
581         stencilFunc->setOperation(sfailop, zfailop, passop);
582
583         // Add the operation to pass
584         pass->setAttributeAndModes(stencilFunc.get());
585     }
586 };
587
588 InstallAttributeBuilder<StencilBuilder> installStencil("stencil");
589
590
591 EffectNameValue<AlphaFunc::ComparisonFunction> alphaComparisonInit[] =
592 {
593     {"never", AlphaFunc::NEVER},
594     {"less", AlphaFunc::LESS},
595     {"equal", AlphaFunc::EQUAL},
596     {"lequal", AlphaFunc::LEQUAL},
597     {"greater", AlphaFunc::GREATER},
598     {"notequal", AlphaFunc::NOTEQUAL},
599     {"gequal", AlphaFunc::GEQUAL},
600     {"always", AlphaFunc::ALWAYS}
601 };
602 EffectPropertyMap<AlphaFunc::ComparisonFunction>
603 alphaComparison(alphaComparisonInit);
604
605 struct AlphaTestBuilder : public PassAttributeBuilder
606 {
607     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
608                         const SGReaderWriterXMLOptions* options)
609     {
610         if (!isAttributeActive(effect, prop))
611             return;
612         // XXX Compatibility with early <alpha-test> syntax; should go away
613         // before a release
614         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
615         if (!realProp)
616             return;
617         if (realProp->nChildren() == 0) {
618             pass->setMode(GL_ALPHA_TEST, (realProp->getBoolValue()
619                                      ? StateAttribute::ON
620                                      : StateAttribute::OFF));
621             return;
622         }
623
624         const SGPropertyNode* pmode = getEffectPropertyChild(effect, prop,
625                                                              "mode");
626         // XXX When dynamic parameters are supported, this code should
627         // create the blend function even if the mode is off.
628         if (pmode && !pmode->getValue<bool>()) {
629             pass->setMode(GL_ALPHA_TEST, StateAttribute::OFF);
630             return;
631         }
632         const SGPropertyNode* pComp = getEffectPropertyChild(effect, prop,
633                                                              "comparison");
634         const SGPropertyNode* pRef = getEffectPropertyChild(effect, prop,
635                                                              "reference");
636         AlphaFunc::ComparisonFunction func = AlphaFunc::ALWAYS;
637         float refValue = 1.0f;
638         if (pComp)
639             findAttr(alphaComparison, pComp, func);
640         if (pRef)
641             refValue = pRef->getValue<float>();
642         if (func == AlphaFunc::GREATER && osg::equivalent(refValue, 1.0f)) {
643             pass->setAttributeAndModes(StateAttributeFactory::instance()
644                                        ->getStandardAlphaFunc());
645         } else {
646             AlphaFunc* alphaFunc = new AlphaFunc;
647             alphaFunc->setFunction(func);
648             alphaFunc->setReferenceValue(refValue);
649             pass->setAttributeAndModes(alphaFunc);
650         }
651     }
652 };
653
654 InstallAttributeBuilder<AlphaTestBuilder> installAlphaTest("alpha-test");
655
656 InstallAttributeBuilder<TextureUnitBuilder> textureUnitBuilder("texture-unit");
657
658 // Shader key, used both for shaders with relative and absolute names
659 typedef pair<string, Shader::Type> ShaderKey;
660
661 struct ProgramKey
662 {
663     typedef pair<string, int> AttribKey;
664     osgDB::FilePathList paths;
665     vector<ShaderKey> shaders;
666     vector<AttribKey> attributes;
667     struct EqualTo
668     {
669         bool operator()(const ProgramKey& lhs, const ProgramKey& rhs) const
670         {
671             return (lhs.paths.size() == rhs.paths.size()
672                     && equal(lhs.paths.begin(), lhs.paths.end(),
673                              rhs.paths.begin())
674                     && lhs.shaders.size() == rhs.shaders.size()
675                     && equal (lhs.shaders.begin(), lhs.shaders.end(),
676                               rhs.shaders.begin())
677                     && lhs.attributes.size() == rhs.attributes.size()
678                     && equal(lhs.attributes.begin(), lhs.attributes.end(),
679                              rhs.attributes.begin()));
680         }
681     };
682 };
683
684 size_t hash_value(const ProgramKey& key)
685 {
686     size_t seed = 0;
687     boost::hash_range(seed, key.paths.begin(), key.paths.end());
688     boost::hash_range(seed, key.shaders.begin(), key.shaders.end());
689     boost::hash_range(seed, key.attributes.begin(), key.attributes.end());
690     return seed;
691 }
692
693 // XXX Should these be protected by a mutex? Probably
694
695 typedef tr1::unordered_map<ProgramKey, ref_ptr<Program>,
696                            boost::hash<ProgramKey>, ProgramKey::EqualTo>
697 ProgramMap;
698 ProgramMap programMap;
699
700 typedef tr1::unordered_map<ShaderKey, ref_ptr<Shader>, boost::hash<ShaderKey> >
701 ShaderMap;
702 ShaderMap shaderMap;
703
704 void reload_shaders()
705 {
706     for(ShaderMap::iterator sitr = shaderMap.begin(); sitr != shaderMap.end(); ++sitr)
707     {
708         Shader *shader = sitr->second.get();
709         string fileName = osgDB::findDataFile(sitr->first.first);
710         if (!fileName.empty()) {
711             shader->loadShaderSourceFromFile(fileName);
712         }
713     }
714 }
715
716 struct ShaderProgramBuilder : PassAttributeBuilder
717 {
718     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
719                         const SGReaderWriterXMLOptions* options);
720 };
721
722 void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass,
723                                           const SGPropertyNode* prop,
724                                           const SGReaderWriterXMLOptions*
725                                           options)
726 {
727     using namespace boost;
728     if (!isAttributeActive(effect, prop))
729         return;
730     PropertyList pVertShaders = prop->getChildren("vertex-shader");
731     PropertyList pFragShaders = prop->getChildren("fragment-shader");
732     PropertyList pAttributes = prop->getChildren("attribute");
733     ProgramKey prgKey;
734     for (PropertyList::iterator itr = pVertShaders.begin(),
735              e = pVertShaders.end();
736          itr != e;
737          ++itr)
738         prgKey.shaders.push_back(ShaderKey((*itr)->getStringValue(),
739                                            Shader::VERTEX));
740     for (PropertyList::iterator itr = pFragShaders.begin(),
741              e = pFragShaders.end();
742          itr != e;
743          ++itr)
744         prgKey.shaders.push_back(ShaderKey((*itr)->getStringValue(),
745                                            Shader::FRAGMENT));
746     for (PropertyList::iterator itr = pAttributes.begin(),
747              e = pAttributes.end();
748          itr != e;
749          ++itr) {
750         const SGPropertyNode* pName = getEffectPropertyChild(effect, *itr,
751                                                              "name");
752         const SGPropertyNode* pIndex = getEffectPropertyChild(effect, *itr,
753                                                               "index");
754         if (!pName || ! pIndex)
755             throw BuilderException("malformed attribute property");
756         prgKey.attributes
757             .push_back(ProgramKey::AttribKey(pName->getStringValue(),
758                                              pIndex->getValue<int>()));
759     }
760     if (options)
761         prgKey.paths = options->getDatabasePathList();
762     Program* program = 0;
763     ProgramMap::iterator pitr = programMap.find(prgKey);
764     if (pitr != programMap.end()) {
765         program = pitr->second.get();
766     } else {
767         program = new Program;
768         // Add vertex shaders, then fragment shaders
769         PropertyList& pvec = pVertShaders;
770         Shader::Type stype = Shader::VERTEX;
771         for (int i = 0; i < 2; ++i) {
772             for (PropertyList::iterator nameItr = pvec.begin(), e = pvec.end();
773                  nameItr != e;
774                  ++nameItr) {
775                 string shaderName = (*nameItr)->getStringValue();
776                 string fileName = osgDB::findDataFile(shaderName, options);
777                 if (fileName.empty())
778                     throw BuilderException(string("couldn't find shader ") +
779                                            shaderName);
780                 ShaderKey skey(fileName, stype);
781                 ShaderMap::iterator sitr = shaderMap.find(skey);
782                 if (sitr != shaderMap.end()) {
783                     program->addShader(sitr->second.get());
784                 } else {
785                     ref_ptr<Shader> shader = new Shader(stype);
786                     if (shader->loadShaderSourceFromFile(fileName)) {
787                         program->addShader(shader.get());
788                         shaderMap.insert(ShaderMap::value_type(skey, shader));
789                     }
790                 }
791             }
792             pvec = pFragShaders;
793             stype = Shader::FRAGMENT;
794         }
795         BOOST_FOREACH(const ProgramKey::AttribKey& key, prgKey.attributes) {
796             program->addBindAttribLocation(key.first, key.second);
797         }
798        programMap.insert(ProgramMap::value_type(prgKey, program));
799     }
800     pass->setAttributeAndModes(program);
801 }
802
803 InstallAttributeBuilder<ShaderProgramBuilder> installShaderProgram("program");
804
805 EffectNameValue<Uniform::Type> uniformTypesInit[] =
806 {
807     {"float", Uniform::FLOAT},
808     {"float-vec3", Uniform::FLOAT_VEC3},
809     {"float-vec4", Uniform::FLOAT_VEC4},
810     {"sampler-1d", Uniform::SAMPLER_1D},
811     {"sampler-2d", Uniform::SAMPLER_2D},
812     {"sampler-3d", Uniform::SAMPLER_3D}
813 };
814 EffectPropertyMap<Uniform::Type> uniformTypes(uniformTypesInit);
815
816 struct UniformBuilder :public PassAttributeBuilder
817 {
818     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
819                         const SGReaderWriterXMLOptions* options)
820     {
821         if (!isAttributeActive(effect, prop))
822             return;
823         const SGPropertyNode* nameProp = prop->getChild("name");
824         const SGPropertyNode* typeProp = prop->getChild("type");
825         const SGPropertyNode* valProp
826             = getEffectPropertyChild(effect, prop, "value");
827         string name;
828         Uniform::Type uniformType = Uniform::FLOAT;
829         if (nameProp) {
830             name = nameProp->getStringValue();
831         } else {
832             SG_LOG(SG_INPUT, SG_ALERT, "No name for uniform property ");
833             return;
834         }
835         if (!valProp) {
836             SG_LOG(SG_INPUT, SG_ALERT, "No value for uniform property "
837                    << name);
838             return;
839         }
840         if (!typeProp) {
841             props::Type propType = valProp->getType();
842             switch (propType) {
843             case props::FLOAT:
844             case props::DOUBLE:
845                 break;          // default float type;
846             case props::VEC3D:
847                 uniformType = Uniform::FLOAT_VEC3;
848                 break;
849             case props::VEC4D:
850                 uniformType = Uniform::FLOAT_VEC4;
851                 break;
852             default:
853                 SG_LOG(SG_INPUT, SG_ALERT, "Can't deduce type of uniform "
854                        << name);
855                 return;
856             }
857         } else {
858             findAttr(uniformTypes, typeProp, uniformType);
859         }
860         ref_ptr<Uniform> uniform = new Uniform;
861         uniform->setName(name);
862         uniform->setType(uniformType);
863         switch (uniformType) {
864         case Uniform::FLOAT:
865             uniform->set(valProp->getValue<float>());
866             break;
867         case Uniform::FLOAT_VEC3:
868             uniform->set(toOsg(valProp->getValue<SGVec3d>()));
869             break;
870         case Uniform::FLOAT_VEC4:
871             uniform->set(toOsg(valProp->getValue<SGVec4d>()));
872             break;
873         case Uniform::SAMPLER_1D:
874         case Uniform::SAMPLER_2D:
875         case Uniform::SAMPLER_3D:
876             uniform->set(valProp->getValue<int>());
877             break;
878         default: // avoid compiler warning
879             break;
880         }
881         pass->addUniform(uniform.get());
882     }
883 };
884
885 InstallAttributeBuilder<UniformBuilder> installUniform("uniform");
886
887 // Not sure what to do with "name". At one point I wanted to use it to
888 // order the passes, but I do support render bin and stuff too...
889
890 struct NameBuilder : public PassAttributeBuilder
891 {
892     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
893                         const SGReaderWriterXMLOptions* options)
894     {
895         // name can't use <use>
896         string name = prop->getStringValue();
897         if (!name.empty())
898             pass->setName(name);
899     }
900 };
901
902 InstallAttributeBuilder<NameBuilder> installName("name");
903
904 EffectNameValue<PolygonMode::Mode> polygonModeModesInit[] =
905 {
906     {"fill", PolygonMode::FILL},
907     {"line", PolygonMode::LINE},
908     {"point", PolygonMode::POINT}
909 };
910 EffectPropertyMap<PolygonMode::Mode> polygonModeModes(polygonModeModesInit);
911
912 struct PolygonModeBuilder : public PassAttributeBuilder
913 {
914     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
915                         const SGReaderWriterXMLOptions* options)
916     {
917         if (!isAttributeActive(effect, prop))
918             return;
919         const SGPropertyNode* frontProp
920             = getEffectPropertyChild(effect, prop, "front");
921         const SGPropertyNode* backProp
922             = getEffectPropertyChild(effect, prop, "back");
923         ref_ptr<PolygonMode> pmode = new PolygonMode;
924         PolygonMode::Mode frontMode = PolygonMode::FILL;
925         PolygonMode::Mode backMode = PolygonMode::FILL;
926         if (frontProp) {
927             findAttr(polygonModeModes, frontProp, frontMode);
928             pmode->setMode(PolygonMode::FRONT, frontMode);
929         }
930         if (backProp) {
931             findAttr(polygonModeModes, backProp, backMode);
932             pmode->setMode(PolygonMode::BACK, backMode);
933         }
934         pass->setAttribute(pmode.get());
935     }
936 };
937
938 InstallAttributeBuilder<PolygonModeBuilder> installPolygonMode("polygon-mode");
939
940 struct VertexProgramTwoSideBuilder : public PassAttributeBuilder
941 {
942     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
943                         const SGReaderWriterXMLOptions* options)
944     {
945         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
946         if (!realProp)
947             return;
948         pass->setMode(GL_VERTEX_PROGRAM_TWO_SIDE,
949                       (realProp->getValue<bool>()
950                        ? StateAttribute::ON : StateAttribute::OFF));
951     }
952 };
953
954 InstallAttributeBuilder<VertexProgramTwoSideBuilder>
955 installTwoSide("vertex-program-two-side");
956
957 struct VertexProgramPointSizeBuilder : public PassAttributeBuilder
958 {
959     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
960                         const SGReaderWriterXMLOptions* options)
961     {
962         const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop);
963         if (!realProp)
964             return;
965         pass->setMode(GL_VERTEX_PROGRAM_POINT_SIZE,
966                       (realProp->getValue<bool>()
967                        ? StateAttribute::ON : StateAttribute::OFF));
968     }
969 };
970
971 InstallAttributeBuilder<VertexProgramPointSizeBuilder>
972 installPointSize("vertex-program-point-size");
973
974 EffectNameValue<Depth::Function> depthFunctionInit[] =
975 {
976     {"never", Depth::NEVER},
977     {"less", Depth::LESS},
978     {"equal", Depth::EQUAL},
979     {"lequal", Depth::LEQUAL},
980     {"greater", Depth::GREATER},
981     {"notequal", Depth::NOTEQUAL},
982     {"gequal", Depth::GEQUAL},
983     {"always", Depth::ALWAYS}
984 };
985 EffectPropertyMap<Depth::Function> depthFunction(depthFunctionInit);
986
987 struct DepthBuilder : public PassAttributeBuilder
988 {
989     void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop,
990                         const SGReaderWriterXMLOptions* options)
991     {
992         if (!isAttributeActive(effect, prop))
993             return;
994         ref_ptr<Depth> depth = new Depth;
995         const SGPropertyNode* pfunc
996             = getEffectPropertyChild(effect, prop, "function");
997         if (pfunc) {
998             Depth::Function func = Depth::LESS;
999             findAttr(depthFunction, pfunc, func);
1000             depth->setFunction(func);
1001         }
1002         const SGPropertyNode* pnear
1003             = getEffectPropertyChild(effect, prop, "near");
1004         if (pnear)
1005             depth->setZNear(pnear->getValue<double>());
1006         const SGPropertyNode* pfar
1007             = getEffectPropertyChild(effect, prop, "far");
1008         if (pfar)
1009             depth->setZFar(pnear->getValue<double>());
1010         const SGPropertyNode* pmask
1011             = getEffectPropertyChild(effect, prop, "write-mask");
1012         if (pmask)
1013             depth->setWriteMask(pmask->getValue<bool>());
1014         pass->setAttribute(depth.get());
1015     }
1016 };
1017
1018 InstallAttributeBuilder<DepthBuilder> installDepth("depth");
1019
1020 void buildTechnique(Effect* effect, const SGPropertyNode* prop,
1021                     const SGReaderWriterXMLOptions* options)
1022 {
1023     Technique* tniq = new Technique;
1024     effect->techniques.push_back(tniq);
1025     const SGPropertyNode* predProp = prop->getChild("predicate");
1026     if (!predProp) {
1027         tniq->setAlwaysValid(true);
1028     } else {
1029         try {
1030             TechniquePredParser parser;
1031             parser.setTechnique(tniq);
1032             expression::BindingLayout& layout = parser.getBindingLayout();
1033             /*int contextLoc = */layout.addBinding("__contextId", expression::INT);
1034             SGExpressionb* validExp
1035                 = dynamic_cast<SGExpressionb*>(parser.read(predProp
1036                                                            ->getChild(0)));
1037             if (validExp)
1038                 tniq->setValidExpression(validExp, layout);
1039             else
1040                 throw expression::ParseError("technique predicate is not a boolean expression");
1041         }
1042         catch (expression::ParseError& except)
1043         {
1044             SG_LOG(SG_INPUT, SG_ALERT,
1045                    "parsing technique predicate " << except.getMessage());
1046             tniq->setAlwaysValid(false);
1047         }
1048     }
1049     PropertyList passProps = prop->getChildren("pass");
1050     for (PropertyList::iterator itr = passProps.begin(), e = passProps.end();
1051          itr != e;
1052          ++itr) {
1053         buildPass(effect, tniq, itr->ptr(), options);
1054     }
1055 }
1056
1057 // Specifically for .ac files...
1058 bool makeParametersFromStateSet(SGPropertyNode* effectRoot, const StateSet* ss)
1059 {
1060     SGPropertyNode* paramRoot = makeChild(effectRoot, "parameters");
1061     SGPropertyNode* matNode = paramRoot->getChild("material", 0, true);
1062     Vec4f ambVal, difVal, specVal, emisVal;
1063     float shininess = 0.0f;
1064     const Material* mat = getStateAttribute<Material>(ss);
1065     if (mat) {
1066         ambVal = mat->getAmbient(Material::FRONT_AND_BACK);
1067         difVal = mat->getDiffuse(Material::FRONT_AND_BACK);
1068         specVal = mat->getSpecular(Material::FRONT_AND_BACK);
1069         emisVal = mat->getEmission(Material::FRONT_AND_BACK);
1070         shininess = mat->getShininess(Material::FRONT_AND_BACK);
1071         makeChild(matNode, "active")->setValue(true);
1072         makeChild(matNode, "ambient")->setValue(toVec4d(toSG(ambVal)));
1073         makeChild(matNode, "diffuse")->setValue(toVec4d(toSG(difVal)));
1074         makeChild(matNode, "specular")->setValue(toVec4d(toSG(specVal)));
1075         makeChild(matNode, "emissive")->setValue(toVec4d(toSG(emisVal)));
1076         makeChild(matNode, "shininess")->setValue(shininess);
1077         matNode->getChild("color-mode", 0, true)->setStringValue("diffuse");
1078     } else {
1079         makeChild(matNode, "active")->setValue(false);
1080     }
1081     const ShadeModel* sm = getStateAttribute<ShadeModel>(ss);
1082     string shadeModelString("smooth");
1083     if (sm) {
1084         ShadeModel::Mode smMode = sm->getMode();
1085         if (smMode == ShadeModel::FLAT)
1086             shadeModelString = "flat";
1087     }
1088     makeChild(paramRoot, "shade-model")->setStringValue(shadeModelString);
1089     string cullFaceString("off");
1090     const CullFace* cullFace = getStateAttribute<CullFace>(ss);
1091     if (cullFace) {
1092         switch (cullFace->getMode()) {
1093         case CullFace::FRONT:
1094             cullFaceString = "front";
1095             break;
1096         case CullFace::BACK:
1097             cullFaceString = "back";
1098             break;
1099         case CullFace::FRONT_AND_BACK:
1100             cullFaceString = "front-back";
1101             break;
1102         default:
1103             break;
1104         }
1105     }
1106     makeChild(paramRoot, "cull-face")->setStringValue(cullFaceString);
1107     const BlendFunc* blendFunc = getStateAttribute<BlendFunc>(ss);
1108     SGPropertyNode* blendNode = makeChild(paramRoot, "blend");
1109     if (blendFunc) {
1110         string sourceMode = findName(blendFuncModes, blendFunc->getSource());
1111         string destMode = findName(blendFuncModes, blendFunc->getDestination());
1112         makeChild(blendNode, "active")->setValue(true);
1113         makeChild(blendNode, "source")->setStringValue(sourceMode);
1114         makeChild(blendNode, "destination")->setStringValue(destMode);
1115         makeChild(blendNode, "mode")->setValue(true);
1116     } else {
1117         makeChild(blendNode, "active")->setValue(false);
1118     }
1119     string renderingHint = findName(renderingHints, ss->getRenderingHint());
1120     makeChild(paramRoot, "rendering-hint")->setStringValue(renderingHint);
1121     makeTextureParameters(paramRoot, ss);
1122     return true;
1123 }
1124
1125 // Walk the techniques property tree, building techniques and
1126 // passes.
1127 bool Effect::realizeTechniques(const SGReaderWriterXMLOptions* options)
1128 {
1129     if (_isRealized)
1130         return true;
1131     PropertyList tniqList = root->getChildren("technique");
1132     for (PropertyList::iterator itr = tniqList.begin(), e = tniqList.end();
1133          itr != e;
1134          ++itr)
1135         buildTechnique(this, *itr, options);
1136     _isRealized = true;
1137     return true;
1138 }
1139
1140 void Effect::InitializeCallback::doUpdate(osg::Node* node, osg::NodeVisitor* nv)
1141 {
1142     EffectGeode* eg = dynamic_cast<EffectGeode*>(node);
1143     if (!eg)
1144         return;
1145     Effect* effect = eg->getEffect();
1146     if (!effect)
1147         return;
1148     SGPropertyNode* root = getPropertyRoot();
1149     for (vector<SGSharedPtr<Updater> >::iterator itr = effect->_extraData.begin(),
1150              end = effect->_extraData.end();
1151          itr != end;
1152          ++itr) {
1153         InitializeWhenAdded* adder
1154             = dynamic_cast<InitializeWhenAdded*>(itr->ptr());
1155         if (adder)
1156             adder->initOnAdd(effect, root);
1157     }
1158 }
1159
1160 bool Effect::Key::EqualTo::operator()(const Effect::Key& lhs,
1161                                       const Effect::Key& rhs) const
1162 {
1163     if (lhs.paths.size() != rhs.paths.size()
1164         || !equal(lhs.paths.begin(), lhs.paths.end(), rhs.paths.begin()))
1165         return false;
1166     if (lhs.unmerged.valid() && rhs.unmerged.valid())
1167         return props::Compare()(lhs.unmerged, rhs.unmerged);
1168     else
1169         return lhs.unmerged == rhs.unmerged;
1170 }
1171
1172 size_t hash_value(const Effect::Key& key)
1173 {
1174     size_t seed = 0;
1175     if (key.unmerged.valid())
1176         boost::hash_combine(seed, *key.unmerged);
1177     boost::hash_range(seed, key.paths.begin(), key.paths.end());
1178     return seed;
1179 }
1180
1181 bool Effect_writeLocalData(const Object& obj, osgDB::Output& fw)
1182 {
1183     const Effect& effect = static_cast<const Effect&>(obj);
1184
1185     fw.indent() << "techniques " << effect.techniques.size() << "\n";
1186     BOOST_FOREACH(const ref_ptr<Technique>& technique, effect.techniques) {
1187         fw.writeObject(*technique);
1188     }
1189     return true;
1190 }
1191
1192 namespace
1193 {
1194 osgDB::RegisterDotOsgWrapperProxy effectProxy
1195 (
1196     new Effect,
1197     "simgear::Effect",
1198     "Object simgear::Effect",
1199     0,
1200     &Effect_writeLocalData
1201     );
1202 }
1203
1204 // Property expressions for technique predicates
1205 class PropertyExpression : public SGExpression<bool>
1206 {
1207 public:
1208     PropertyExpression(SGPropertyNode* pnode) : _pnode(pnode) {}
1209     
1210     void eval(bool& value, const expression::Binding*) const
1211     {
1212         value = _pnode->getValue<bool>();
1213     }
1214 protected:
1215     SGPropertyNode_ptr _pnode;
1216 };
1217
1218 class EffectPropertyListener : public SGPropertyChangeListener
1219 {
1220 public:
1221     EffectPropertyListener(Technique* tniq) : _tniq(tniq) {}
1222     
1223     void valueChanged(SGPropertyNode* node)
1224     {
1225         _tniq->refreshValidity();
1226     }
1227 protected:
1228     osg::ref_ptr<Technique> _tniq;
1229 };
1230
1231 Expression* propertyExpressionParser(const SGPropertyNode* exp,
1232                                      expression::Parser* parser)
1233 {
1234     SGPropertyNode_ptr pnode = getPropertyRoot()->getNode(exp->getStringValue(),
1235                                                           true);
1236     PropertyExpression* pexp = new PropertyExpression(pnode);
1237     TechniquePredParser* predParser
1238         = dynamic_cast<TechniquePredParser*>(parser);
1239     if (predParser)
1240         pnode->addChangeListener(new EffectPropertyListener(predParser
1241                                                             ->getTechnique()));
1242     return pexp;
1243 }
1244
1245 expression::ExpParserRegistrar propertyRegistrar("property",
1246                                                  propertyExpressionParser);
1247
1248 }