diff --git a/acinclude.m4 b/acinclude.m4 index af9004cb..9e46179f 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -671,7 +671,7 @@ if test "x$want_boost" = "xyes"; then if test "$_version" = "0" ; then AC_MSG_ERROR([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in . See http://randspringer.de/boost for more documentation.]]) else - AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).]) + AC_MSG_ERROR([Your boost libraries seems to old (version $_version).]) fi else AC_SUBST(BOOST_CPPFLAGS) diff --git a/configure.ac b/configure.ac index e82399f4..ca19e4cf 100644 --- a/configure.ac +++ b/configure.ac @@ -37,7 +37,7 @@ AC_PROG_CXX AC_PROG_RANLIB AC_PROG_INSTALL AC_PROG_LN_S -AX_BOOST_BASE([1.34.0]) +AX_BOOST_BASE([1.37.0]) if test "x$BOOST_CPPFLAGS" != "x-I/usr/include" ; then CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" diff --git a/projects/VC7.1/SimGear.vcproj b/projects/VC7.1/SimGear.vcproj index 36dc8e09..783ac4d0 100755 --- a/projects/VC7.1/SimGear.vcproj +++ b/projects/VC7.1/SimGear.vcproj @@ -643,6 +643,15 @@ + + + + + + @@ -1171,6 +1180,12 @@ + + + + @@ -1231,12 +1246,24 @@ + + + + + + + + + + + + @@ -991,6 +999,11 @@ RelativePath="..\..\simgear\props\condition.hxx" > + + + @@ -1705,6 +1718,14 @@ + + + + @@ -1785,6 +1806,14 @@ RelativePath="..\..\simgear\scene\util\SGUpdateVisitor.hxx" > + + + + @@ -1793,6 +1822,14 @@ RelativePath="..\..\simgear\scene\util\StateAttributeFactory.hxx" > + + + + +#include +#include + +#include + +#include + +namespace simgear +{ +using namespace std; + +MultiChangeListener::MultiChangeListener() +{ +} + +void MultiChangeListener::valueChanged(SGPropertyNode* node) +{ + valueChangedImplementation(); +} + +void MultiChangeListener::valueChangedImplementation() +{ +} + +AtomicChangeListener::AtomicChangeListener(std::vector& nodes) + : _dirty(false), _valid(true) +{ + listenToProperties(nodes.begin(), nodes.end()); +} + +void AtomicChangeListener::unregister_property(SGPropertyNode* node) +{ + _valid = false; + // not necessary, but good hygine + vector::iterator itr + = find(_watched.begin(), _watched.end(), node); + if (itr != _watched.end()) + *itr = 0; + MultiChangeListener::unregister_property(node); +} + +void AtomicChangeListener::fireChangeListeners() +{ + vector >& listeners + = ListenerListSingleton::instance()->listeners; + for (vector >::iterator itr = listeners.begin(), + end = listeners.end(); + itr != end; + ++itr) { + (*itr)->valuesChanged(); + (*itr)->_dirty = false; + } + listeners.clear(); +} + +void AtomicChangeListener::valueChangedImplementation() +{ + if (!_dirty) { + _dirty = true; + if (_valid) + ListenerListSingleton::instance()->listeners.push_back(this); + } +} + +void AtomicChangeListener::valuesChanged() +{ +} +} diff --git a/simgear/props/AtomicChangeListener.hxx b/simgear/props/AtomicChangeListener.hxx new file mode 100644 index 00000000..a287738c --- /dev/null +++ b/simgear/props/AtomicChangeListener.hxx @@ -0,0 +1,104 @@ +#ifndef SIMGEAR_ATOMICCHANGELISTENER_HXX +#define SIMGEAR_ATOMICCHANGELISTENER_HXX 1 + +#include +#include +#include + +#include + +#include + +#include "props.hxx" +#include "ExtendedPropertyAdapter.hxx" + +namespace simgear +{ +// Performs an action when one of several nodes changes +class MultiChangeListener : public SGPropertyChangeListener +{ +public: + MultiChangeListener(); + template + void listenToProperties(Pitr propsBegin, Pitr propsEnd) + { + for (Pitr itr = propsBegin, end = propsEnd; itr != end; ++itr) + (*itr)->addChangeListener(this); + } +private: + void valueChanged(SGPropertyNode* node); + virtual void valueChangedImplementation(); + +}; + +class AtomicChangeListener : public MultiChangeListener, + public virtual SGReferenced +{ +public: + AtomicChangeListener(std::vector& nodes); + /** + * Lookup / create child nodes from their relative names. + */ + template + AtomicChangeListener(SGPropertyNode* parent, Itr childNamesBegin, + Itr childNamesEnd) + : _dirty(false), _valid(true) + { + using namespace std; + for (Itr itr = childNamesBegin, end = childNamesEnd; + itr != end; + ++itr) + _watched.push_back(makeNode(parent, *itr)); + listenToProperties(_watched.begin(), _watched.end()); + } + bool isDirty() { return _dirty; } + bool isValid() { return _valid; } + void unregister_property(SGPropertyNode* node); + static void fireChangeListeners(); +private: + virtual void valueChangedImplementation(); + virtual void valuesChanged(); + bool _dirty; + bool _valid; + struct ListenerListSingleton : public Singleton + { + std::vector > listeners; + }; +protected: + std::vector _watched; +}; + +template +class ExtendedPropListener : public AtomicChangeListener +{ +public: + ExtendedPropListener(std::vector& nodes, const Func& func, + bool initial = false) + : AtomicChangeListener(nodes), _func(func) + { + if (initial) + valuesChanged(); + + } + template + ExtendedPropListener(SGPropertyNode* parent, Itr childNamesBegin, + Itr childNamesEnd, const Func& func, + bool initial = false) + : AtomicChangeListener(parent, childNamesBegin, childNamesEnd), + _func(func) + { + if (initial) + valuesChanged(); + } + virtual void valuesChanged() + { + ExtendedPropertyAdapter > adaptor(_watched); + T val = adaptor(); + _func(val); + } +private: + Func _func; +}; + +} +#endif diff --git a/simgear/props/ExtendedPropertyAdapter.hxx b/simgear/props/ExtendedPropertyAdapter.hxx new file mode 100644 index 00000000..064f4074 --- /dev/null +++ b/simgear/props/ExtendedPropertyAdapter.hxx @@ -0,0 +1,72 @@ +#ifndef SIMGEAR_EXTENDEDPROPERTYADAPTER_HXX +#define SIMGEAR_EXTENDEDPROPERTYADAPTER_HXX 1 + +#include + +#include + +#include +#include + +#include "props.hxx" + +namespace simgear +{ + +namespace props +{ +// This should be in simgear/math/SGVec.hxx and friends + +template struct NumComponents; + +template<> struct NumComponents +{ + enum { num_components = 3 }; +}; + +template<> struct NumComponents +{ + enum { num_components = 4 }; +}; + +} + +template +class ExtendedPropertyAdapter +{ +public: + enum { num_components = props::NumComponents::num_components }; + ExtendedPropertyAdapter(const NodeContainer& elements) + : _elements(elements) + { + } + T operator()() const + { + T result; + if (_elements.size() < num_components) + throw sg_exception(); + for (int i = 0; i < num_components; ++i) + result[i] = _elements[i]->getValue(); + return result; + } + void set(const T& val) + { + if (_elements.size() < num_components) + throw sg_exception(); + for (int i = 0; i < num_components; ++i) + _elements[i]->setValue(val[i]); + } +private: + const NodeContainer& _elements; +}; + +template +inline void makeChildList(SGPropertyNode* prop, InIterator inBegin, + InIterator inEnd, OutIterator outBegin) +{ + std::transform(inBegin, inEnd, outBegin, + boost::bind(static_cast(&SGPropertyNode::getChild), prop, _1, 0, true)); +} + +} +#endif diff --git a/simgear/props/Makefile.am b/simgear/props/Makefile.am index 8e726f15..acf52017 100644 --- a/simgear/props/Makefile.am +++ b/simgear/props/Makefile.am @@ -5,12 +5,15 @@ lib_LIBRARIES = libsgprops.a include_HEADERS = \ condition.hxx \ props.hxx \ - props_io.hxx + props_io.hxx \ + AtomicChangeListener.hxx \ + ExtendedPropertyAdapter.hxx libsgprops_a_SOURCES = \ condition.cxx \ props.cxx \ - props_io.cxx + props_io.cxx \ + AtomicChangeListener.cxx noinst_PROGRAMS = props_test diff --git a/simgear/props/props.cxx b/simgear/props/props.cxx index 0bc56dcd..c662eb7f 100644 --- a/simgear/props/props.cxx +++ b/simgear/props/props.cxx @@ -16,9 +16,17 @@ #include #include +#include #include #include +#include +#include +#include +#include +#include +#include + #include #if PROPS_STANDALONE @@ -78,49 +86,36 @@ public: // Local path normalization code. //////////////////////////////////////////////////////////////////////// -/** - * A component in a path. - */ -struct PathComponent -{ - string name; - int index; -}; - /** * Parse the name for a path component. * * Name: [_a-zA-Z][-._a-zA-Z0-9]* */ -static inline const string -parse_name (const string &path, int &i) + +template +inline Range +parse_name (const Range &path) { - string name = ""; - int max = path.size(); + typename Range::iterator i = path.begin(); + typename Range::iterator max = path.end(); - if (path[i] == '.') { + if (*i == '.') { i++; - if (i < max && path[i] == '.') { + if (i != path.end() && *i == '.') { i++; - name = ".."; - } else { - name = "."; } - if (i < max && path[i] != '/') - throw string("illegal character after " + name); - } - - else if (isalpha(path[i]) || path[i] == '_') { - name += path[i]; + if (i != max && *i != '/') + throw string("illegal character after . or .."); + } else if (isalpha(*i) || *i == '_') { i++; // The rules inside a name are a little // less restrictive. - while (i < max) { - if (isalpha(path[i]) || isdigit(path[i]) || path[i] == '_' || - path[i] == '-' || path[i] == '.') { - name += path[i]; - } else if (path[i] == '[' || path[i] == '/') { + while (i != max) { + if (isalpha(*i) || isdigit(*i) || *i == '_' || + *i == '-' || *i == '.') { + // name += path[i]; + } else if (*i == '[' || *i == '/') { break; } else { throw string("name may contain only ._- and alphanumeric characters"); @@ -130,90 +125,24 @@ parse_name (const string &path, int &i) } else { - if (name.size() == 0) + if (path.begin() == i) throw string("name must begin with alpha or '_'"); } - - return name; + return Range(path.begin(), i); } - -/** - * Parse the optional integer index for a path component. - * - * Index: "[" [0-9]+ "]" - */ -static inline int -parse_index (const string &path, int &i) +// Validate the name of a single node +inline bool validateName(const string& name) { - int index = 0; - - if (path[i] != '[') - return 0; - else - i++; - - for (int max = path.size(); i < max; i++) { - if (isdigit(path[i])) { - index = (index * 10) + (path[i] - '0'); - } else if (path[i] == ']') { - i++; - return index; - } else { - break; - } - } - - throw string("unterminated index (looking for ']')"); + using namespace boost; + if (name.empty()) + return false; + if (!isalpha(name[0]) && name[0] != '_') + return false; + return all(make_iterator_range(name.begin(), name.end()), + is_alnum() || is_any_of("_-.")); } - -/** - * Parse a single path component. - * - * Component: Name Index? - */ -static inline PathComponent -parse_component (const string &path, int &i) -{ - PathComponent component; - component.name = parse_name(path, i); - if (component.name[0] != '.') - component.index = parse_index(path, i); - else - component.index = -1; - return component; -} - - -/** - * Parse a path into its components. - */ -static void -parse_path (const string &path, vector &components) -{ - int pos = 0; - int max = path.size(); - - // Check for initial '/' - if (path[pos] == '/') { - PathComponent root; - root.name = ""; - root.index = -1; - components.push_back(root); - pos++; - while (pos < max && path[pos] == '/') - pos++; - } - - while (pos < max) { - components.push_back(parse_component(path, pos)); - while (pos < max && path[pos] == '/') - pos++; - } -} - - //////////////////////////////////////////////////////////////////////// // Other static utility functions. @@ -242,16 +171,18 @@ compare_strings (const char * s1, const char * s2) /** * Locate a child node by name and index. */ +template static int -find_child (const char * name, int index, const PropertyList& nodes) +find_child (Itr begin, Itr end, int index, const PropertyList& nodes) { int nNodes = nodes.size(); + boost::iterator_range name(begin, end); for (int i = 0; i < nNodes; i++) { SGPropertyNode * node = nodes[i]; // searching for a mathing index is a lot less time consuming than // comparing two strings so do that first. - if (node->getIndex() == index && compare_strings(node->getName(), name)) + if (node->getIndex() == index && boost::equals(node->getName(), name)) return i; } return -1; @@ -277,55 +208,137 @@ find_last_child (const char * name, const PropertyList& nodes) return index; } - -/** - * Locate another node, given a relative path. - */ -static SGPropertyNode * -find_node (SGPropertyNode * current, - const vector &components, - int position, - bool create) +template +inline SGPropertyNode* +SGPropertyNode::getExistingChild (Itr begin, Itr end, int index, bool create) { - // Run off the end of the list + int pos = find_child(begin, end, index, _children); + if (pos >= 0) { + return _children[pos]; + } else if (create) { + SGPropertyNode_ptr node; + pos = find_child(begin, end, index, _removedChildren); + if (pos >= 0) { + PropertyList::iterator it = _removedChildren.begin(); + it += pos; + node = _removedChildren[pos]; + _removedChildren.erase(it); + node->setAttribute(REMOVED, false); + _children.push_back(node); + fireChildAdded(node); + return node; + } + } + return 0; +} + +template +SGPropertyNode * +SGPropertyNode::getChildImpl (Itr begin, Itr end, int index, bool create) +{ + SGPropertyNode* node = getExistingChild(begin, end, index, create); + + if (node) { + return node; + } else if (create) { + node = new SGPropertyNode(begin, end, index, this); + _children.push_back(node); + fireChildAdded(node); + return node; + } else { + return 0; + } +} + +template +SGPropertyNode* +find_node_aux(SGPropertyNode * current, SplitItr& itr, bool create, + int last_index) +{ + typedef typename SplitItr::value_type Range; + // Run off the end of the list if (current == 0) { return 0; } - // Success! This is the one we want. - else if (position >= (int)components.size()) { - return (current->getAttribute(SGPropertyNode::REMOVED) ? 0 : current); - } - - // Empty component means root. - else if (components[position].name == "") { - return find_node(current->getRootNode(), components, position + 1, create); - } - - // . means current directory - else if (components[position].name == ".") { - return find_node(current, components, position + 1, create); - } - - // .. means parent directory - else if (components[position].name == "..") { + // Success! This is the one we want. + if (itr.eof()) + return current; + Range token = *itr; + // Empty name at this point is empty, not root. + if (token.empty()) + return find_node_aux(current, ++itr, create, last_index); + Range name = parse_name(token); + if (equals(name, ".")) + return find_node_aux(current, ++itr, create, last_index); + if (equals(name, "..")) { SGPropertyNode * parent = current->getParent(); if (parent == 0) throw string("attempt to move past root with '..'"); - else - return find_node(parent, components, position + 1, create); + return find_node_aux(parent, ++itr, create, last_index); + } + int index = -1; + if (last_index >= 0) { + // If we are at the last token and last_index is valid, use + // last_index as the index value + bool lastTok = true; + while (!(++itr).eof()) { + if (!itr->empty()) { + lastTok = false; + break; + } + } + if (lastTok) + index = last_index; + } else { + ++itr; } - // Otherwise, a child name - else { - SGPropertyNode * child = - current->getChild(components[position].name.c_str(), - components[position].index, - create); - return find_node(child, components, position + 1, create); + if (index < 0) { + index = 0; + if (name.end() != token.end()) { + if (*name.end() == '[') { + typename Range::iterator i = name.end() + 1, end = token.end(); + for (;i != end; ++i) { + if (isdigit(*i)) { + index = (index * 10) + (*i - '0'); + } else { + break; + } + } + if (i == token.end() || *i != ']') + throw string("unterminated index (looking for ']')"); + } else { + throw string("illegal characters in token: ") + + string(name.begin(), name.end()); + } + } } + return find_node_aux(current->getChildImpl(name.begin(), name.end(), + index, create), itr, create, + last_index); } +// Internal function for parsing property paths. last_index provides +// and index value for the last node name token, if supplied. +template +SGPropertyNode* +find_node (SGPropertyNode * current, + const Range& path, + bool create, + int last_index = -1) +{ + using namespace boost; + typedef split_iterator::type> + PathSplitIterator; + + PathSplitIterator itr + = make_split_iterator(path, first_finder("/", is_equal())); + if (*path.begin() == '/') + return find_node_aux(current->getRootNode(), itr, create, last_index); + else + return find_node_aux(current, itr, create, last_index); +} //////////////////////////////////////////////////////////////////////// @@ -695,10 +708,12 @@ SGPropertyNode::SGPropertyNode (const SGPropertyNode &node) /** * Convenience constructor. */ -SGPropertyNode::SGPropertyNode (const char * name, +template +SGPropertyNode::SGPropertyNode (Itr begin, Itr end, int index, SGPropertyNode * parent) : _index(index), + _name(begin, end), _parent(parent), _path_cache(0), _type(props::NONE), @@ -706,14 +721,29 @@ SGPropertyNode::SGPropertyNode (const char * name, _attr(READ|WRITE), _listeners(0) { - int i = 0; _local_val.string_val = 0; _value.val = 0; - _name = parse_name(name, i); - if (i != int(strlen(name)) || name[0] == '.') - throw string("plain name expected instead of '") + name + '\''; + if (!validateName(_name)) + throw string("plain name expected instead of '") + _name + '\''; } +SGPropertyNode::SGPropertyNode (const string& name, + int index, + SGPropertyNode * parent) + : _index(index), + _name(name), + _parent(parent), + _path_cache(0), + _type(props::NONE), + _tied(false), + _attr(READ|WRITE), + _listeners(0) +{ + _local_val.string_val = 0; + _value.val = 0; + if (!validateName(name)) + throw string("plain name expected instead of '") + _name + '\''; +} /** * Destructor. @@ -801,7 +831,7 @@ SGPropertyNode::addChild (const char * name) int pos = find_last_child(name, _children)+1; SGPropertyNode_ptr node; - node = new SGPropertyNode(name, pos, this); + node = new SGPropertyNode(name, name + strlen(name), pos, this); _children.push_back(node); fireChildAdded(node); return node; @@ -837,32 +867,29 @@ SGPropertyNode::getChild (int position) const /** * Get a non-const child by name and index, creating if necessary. */ + SGPropertyNode * SGPropertyNode::getChild (const char * name, int index, bool create) { - int pos = find_child(name, index, _children); - if (pos >= 0) { - return _children[pos]; - } else if (create) { - SGPropertyNode_ptr node; - pos = find_child(name, index, _removedChildren); - if (pos >= 0) { - PropertyList::iterator it = _removedChildren.begin(); - it += pos; - node = _removedChildren[pos]; - _removedChildren.erase(it); - node->setAttribute(REMOVED, false); - } else { - node = new SGPropertyNode(name, index, this); - } - _children.push_back(node); - fireChildAdded(node); - return node; - } else { - return 0; - } + return getChildImpl(name, name + strlen(name), index, create); } +SGPropertyNode * +SGPropertyNode::getChild (const string& name, int index, bool create) +{ + SGPropertyNode* node = getExistingChild(name.begin(), name.end(), index, + create); + if (node) { + return node; + } else if (create) { + node = new SGPropertyNode(name, index, this); + _children.push_back(node); + fireChildAdded(node); + return node; + } else { + return 0; + } +} /** * Get a const child by name and index. @@ -870,7 +897,7 @@ SGPropertyNode::getChild (const char * name, int index, bool create) const SGPropertyNode * SGPropertyNode::getChild (const char * name, int index) const { - int pos = find_child(name, index, _children); + int pos = find_child(name, name + strlen(name), index, _children); if (pos >= 0) return _children[pos]; else @@ -945,7 +972,7 @@ SGPropertyNode_ptr SGPropertyNode::removeChild (const char * name, int index, bool keep) { SGPropertyNode_ptr ret; - int pos = find_child(name, index, _children); + int pos = find_child(name, name + strlen(name), index, _children); if (pos >= 0) ret = removeChild(pos, keep); return ret; @@ -1710,14 +1737,16 @@ SGPropertyNode::getRootNode () const SGPropertyNode * SGPropertyNode::getNode (const char * relative_path, bool create) { + using namespace boost; if (_path_cache == 0) _path_cache = new hash_table; SGPropertyNode * result = _path_cache->get(relative_path); if (result == 0) { - vector components; - parse_path(relative_path, components); - result = find_node(this, components, 0, create); + result = find_node(this, + make_iterator_range(relative_path, relative_path + + strlen(relative_path)), + create); if (result != 0) _path_cache->put(relative_path, result); } @@ -1728,11 +1757,10 @@ SGPropertyNode::getNode (const char * relative_path, bool create) SGPropertyNode * SGPropertyNode::getNode (const char * relative_path, int index, bool create) { - vector components; - parse_path(relative_path, components); - if (components.size() > 0) - components.back().index = index; - return find_node(this, components, 0, create); + using namespace boost; + return find_node(this, make_iterator_range(relative_path, relative_path + + strlen(relative_path)), + create, index); } const SGPropertyNode * @@ -2354,6 +2382,146 @@ std::istream& readFrom(std::istream& stream, SGVec4d& result) } return stream; } + +namespace +{ +bool compareNodeValue(const SGPropertyNode& lhs, const SGPropertyNode& rhs) +{ + props::Type ltype = lhs.getType(); + props::Type rtype = rhs.getType(); + if (ltype != rtype) + return false; + switch (ltype) { + case props::NONE: + return true; + case props::ALIAS: + return false; // XXX Should we look in aliases? + case props::BOOL: + return lhs.getValue() == rhs.getValue(); + case props::INT: + return lhs.getValue() == rhs.getValue(); + case props::LONG: + return lhs.getValue() == rhs.getValue(); + case props::FLOAT: + return lhs.getValue() == rhs.getValue(); + case props::DOUBLE: + return lhs.getValue() == rhs.getValue(); + case props::STRING: + case props::UNSPECIFIED: + return !strcmp(lhs.getStringValue(), rhs.getStringValue()); + case props::VEC3D: + return lhs.getValue() == rhs.getValue(); + case props::VEC4D: + return lhs.getValue() == rhs.getValue(); + default: + return false; + } +} +} +} + +bool SGPropertyNode::compare(const SGPropertyNode& lhs, + const SGPropertyNode& rhs) +{ + if (&lhs == &rhs) + return true; + int lhsChildren = lhs.nChildren(); + int rhsChildren = rhs.nChildren(); + if (lhsChildren != rhsChildren) + return false; + if (lhsChildren == 0) + return compareNodeValue(lhs, rhs); + for (size_t i = 0; i < lhs._children.size(); ++i) { + const SGPropertyNode* lchild = lhs._children[i]; + const SGPropertyNode* rchild = rhs._children[i]; + // I'm guessing that the nodes will usually be in the same + // order. + if (lchild->getIndex() != rchild->getIndex() + || lchild->getNameString() != rchild->getNameString()) { + rchild = 0; + for (PropertyList::const_iterator itr = rhs._children.begin(), + end = rhs._children.end(); + itr != end; + ++itr) + if (lchild->getIndex() == (*itr)->getIndex() + && lchild->getNameString() == (*itr)->getNameString()) { + rchild = *itr; + break; + } + if (!rchild) + return false; + } + if (!compare(*lchild, *rchild)) + return false; + } + return true; +} + +struct PropertyPlaceLess { + typedef bool result_type; + bool operator()(SGPropertyNode_ptr lhs, SGPropertyNode_ptr rhs) const + { + int comp = lhs->getNameString().compare(rhs->getNameString()); + if (comp == 0) + return lhs->getIndex() < rhs->getIndex(); + else + return comp < 0; + } +}; + +size_t hash_value(const SGPropertyNode& node) +{ + using namespace boost; + + if (node.nChildren() == 0) { + switch (node.getType()) { + case props::NONE: + return 0; + + case props::BOOL: + return hash_value(node.getValue()); + case props::INT: + return hash_value(node.getValue()); + case props::LONG: + return hash_value(node.getValue()); + case props::FLOAT: + return hash_value(node.getValue()); + case props::DOUBLE: + return hash_value(node.getValue()); + case props::STRING: + case props::UNSPECIFIED: + { + const char *val = node.getStringValue(); + return hash_range(val, val + strlen(val)); + } + case props::VEC3D: + { + const SGVec3d val = node.getValue(); + return hash_range(&val[0], &val[3]); + } + case props::VEC4D: + { + const SGVec4d val = node.getValue(); + return hash_range(&val[0], &val[4]); + } + case props::ALIAS: // XXX Should we look in aliases? + default: + return 0; + } + } else { + size_t seed = 0; + PropertyList children(node._children.begin(), node._children.end()); + sort(children.begin(), children.end(), PropertyPlaceLess()); + for (PropertyList::const_iterator itr = children.begin(), + end = children.end(); + itr != end; + ++itr) { + hash_combine(seed, (*itr)->_name); + hash_combine(seed, (*itr)->_index); + hash_combine(seed, hash_value(**itr)); + } + return seed; + } } // end of props.cxx diff --git a/simgear/props/props.hxx b/simgear/props/props.hxx index dc987134..984bb1b5 100644 --- a/simgear/props/props.hxx +++ b/simgear/props/props.hxx @@ -161,6 +161,7 @@ DEFINTERNALPROP(long, LONG); DEFINTERNALPROP(float, FLOAT); DEFINTERNALPROP(double, DOUBLE); DEFINTERNALPROP(const char *, STRING); +DEFINTERNALPROP(const char[], STRING); #undef DEFINTERNALPROP template<> @@ -804,6 +805,11 @@ public: */ const char * getName () const { return _name.c_str(); } + /** + * Get the node's simple name as a string. + */ + const std::string& getNameString () const { return _name; } + /** * Get the node's pretty display name, with subscript when needed. */ @@ -875,17 +881,10 @@ public: /** * Get a child node by name and index. */ - SGPropertyNode * getChild (const char * name, int index = 0, - bool create = false); - - /** - * Get a child node by name and index. - */ + SGPropertyNode * getChild (const char* name, int index = 0, + bool create = false); SGPropertyNode * getChild (const std::string& name, int index = 0, - bool create = false) - { return getChild(name.c_str(), index, create); } - - + bool create = false); /** * Get a const child node by name and index. */ @@ -1223,6 +1222,12 @@ public: bool setValue(const T& val, typename boost::disable_if_c::Internal> ::type* dummy = 0); + + template + bool setValue(const char (&val)[N]) + { + return setValue(&val[0]); + } /** * Print the value of the property to a stream. @@ -1594,6 +1599,16 @@ public: */ void clearValue (); + /** + * Compare two property trees. The property trees are equal if: 1) + * They have no children, and have the same type and the values are + * equal, or 2) have the same number of children, and the + * corresponding children in each tree are equal. "corresponding" + * means have the same name and index. + * + * Attributes, removed children, and aliases aren't considered. + */ + static bool compare (const SGPropertyNode& lhs, const SGPropertyNode& rhs); protected: @@ -1604,12 +1619,13 @@ protected: /** * Protected constructor for making new nodes on demand. */ - SGPropertyNode (const char * name, int index, SGPropertyNode * parent); - + SGPropertyNode (const std::string& name, int index, SGPropertyNode * parent); + template + SGPropertyNode (Itr begin, Itr end, int index, SGPropertyNode * parent); private: - // Get the raw value + // Get the raw value bool get_bool () const; int get_int () const; long get_long () const; @@ -1617,7 +1633,7 @@ private: double get_double () const; const char * get_string () const; - // Set the raw value + // Set the raw value bool set_bool (bool value); bool set_int (int value); bool set_long (long value); @@ -1666,7 +1682,7 @@ private: bool _tied; int _attr; - // The right kind of pointer... + // The right kind of pointer... union { SGPropertyNode * alias; SGRaw* val; @@ -1685,8 +1701,8 @@ private: /** - * Register/unregister node that links to this node in its path cache. - */ + * Register/unregister node that links to this node in its path cache. + */ void add_linked_node (hash_table * node) { _linkedNodes.push_back(node); } bool remove_linked_node (hash_table * node); @@ -1743,7 +1759,18 @@ private: unsigned int _data_length; bucket ** _data; }; - + // Pass name as a pair of iterators + template + SGPropertyNode * getChildImpl (Itr begin, Itr end, int index = 0, bool create = false); + // very internal method + template + SGPropertyNode* getExistingChild (Itr begin, Itr end, int index, bool create); + // very internal path parsing function + template + friend SGPropertyNode* find_node_aux(SGPropertyNode * current, SplitItr& itr, + bool create, int last_index); + // For boost + friend size_t hash_value(const SGPropertyNode& node); }; // Convenience functions for use in templates @@ -1912,13 +1939,69 @@ inline bool SGPropertyNode::setValue(const T& val, } /** - * Utility function for creation of a child property node + * Utility function for creation of a child property node. */ inline SGPropertyNode* makeChild(SGPropertyNode* parent, const char* name, int index = 0) { return parent->getChild(name, index, true); } + +/** + * Utility function for creation of a child property node using a + * relative path. + */ +namespace simgear +{ +template +inline SGPropertyNode* makeNode(SGPropertyNode* parent, const StringType& name) +{ + return parent->getNode(name, true); +} +} + +// For boost::hash +size_t hash_value(const SGPropertyNode& node); + +// Helper comparison and hash functions for common cases + +namespace simgear +{ +namespace props +{ +struct Compare +{ + bool operator()(const SGPropertyNode* lhs, const SGPropertyNode* rhs) const + { + return SGPropertyNode::compare(*lhs, *rhs); + } + bool operator()(SGPropertyNode_ptr lhs, const SGPropertyNode* rhs) const + { + return SGPropertyNode::compare(*lhs, *rhs); + } + bool operator()(const SGPropertyNode* lhs, SGPropertyNode_ptr rhs) const + { + return SGPropertyNode::compare(*lhs, *rhs); + } + bool operator()(SGPropertyNode_ptr lhs, SGPropertyNode_ptr rhs) const + { + return SGPropertyNode::compare(*lhs, *rhs); + } +}; + +struct Hash +{ + size_t operator()(const SGPropertyNode* node) const + { + return hash_value(*node); + } + size_t operator()(SGPropertyNode_ptr node) const + { + return hash_value(*node); + } +}; +} +} #endif // __PROPS_HXX // end of props.hxx diff --git a/simgear/props/props_io.cxx b/simgear/props/props_io.cxx index 232e3c2c..48244c7b 100644 --- a/simgear/props/props_io.cxx +++ b/simgear/props/props_io.cxx @@ -187,16 +187,17 @@ PropsVisitor::startElement (const char * name, const XMLAttributes &atts) // Get the index. attval = atts.getValue("n"); int index = 0; + string strName(name); if (attval != 0) { index = atoi(attval); - st.counters[name] = SG_MAX2(st.counters[name], index+1); + st.counters[strName] = SG_MAX2(st.counters[strName], index+1); } else { - index = st.counters[name]; - st.counters[name]++; + index = st.counters[strName]; + st.counters[strName]++; } // Got the index, so grab the node. - SGPropertyNode * node = st.node->getChild(name, index, true); + SGPropertyNode * node = st.node->getChild(strName, index, true); if (!node->getAttribute(SGPropertyNode::WRITE)) { SG_LOG(SG_INPUT, SG_ALERT, "Not overwriting write-protected property " << node->getPath(true)); @@ -601,6 +602,13 @@ writeProperties (const string &file, const SGPropertyNode * start_node, } } +// Another variation, useful when called from gdb +void +writeProperties (const char* file, const SGPropertyNode * start_node) +{ + writeProperties(string(file), start_node, true); +} + //////////////////////////////////////////////////////////////////////// @@ -678,7 +686,7 @@ copyProperties (const SGPropertyNode *in, SGPropertyNode *out) int nChildren = in->nChildren(); for (int i = 0; i < nChildren; i++) { const SGPropertyNode * in_child = in->getChild(i); - SGPropertyNode * out_child = out->getChild(in_child->getName(), + SGPropertyNode * out_child = out->getChild(in_child->getNameString(), in_child->getIndex(), true); if (!copyProperties(in_child, out_child)) diff --git a/simgear/scene/material/Effect.cxx b/simgear/scene/material/Effect.cxx index b36702ad..940b1f18 100644 --- a/simgear/scene/material/Effect.cxx +++ b/simgear/scene/material/Effect.cxx @@ -20,6 +20,7 @@ #include "Effect.hxx" #include "EffectBuilder.hxx" +#include "EffectGeode.hxx" #include "Technique.hxx" #include "Pass.hxx" #include "TextureBuilder.hxx" @@ -29,16 +30,21 @@ #include #include #include +#include #include #include -#include +#include #include #include +#include +#include #include +#include #include #include +#include #include #include #include @@ -59,6 +65,7 @@ #include #include +#include #include #include #include @@ -72,17 +79,23 @@ using namespace std; using namespace osg; using namespace osgUtil; +using namespace effect; + Effect::Effect() + : _cache(0), _isRealized(false) { } Effect::Effect(const Effect& rhs, const CopyOp& copyop) - : root(rhs.root), parametersProp(rhs.parametersProp) + : root(rhs.root), parametersProp(rhs.parametersProp), _cache(0), + _isRealized(rhs._isRealized) { - using namespace boost; - transform(rhs.techniques.begin(), rhs.techniques.end(), - back_inserter(techniques), - bind(simgear::clone_ref, _1, copyop)); + typedef vector > TechniqueList; + for (TechniqueList::const_iterator itr = rhs.techniques.begin(), + end = rhs.techniques.end(); + itr != end; + ++itr) + techniques.push_back(static_cast(copyop(itr->get()))); } // Assume that the last technique is always valid. @@ -125,29 +138,9 @@ void Effect::releaseGLObjects(osg::State* state) const Effect::~Effect() { + delete _cache; } -class PassAttributeBuilder : public Referenced -{ -public: - virtual void buildAttribute(Effect* effect, Pass* pass, - const SGPropertyNode* prop, - const osgDB::ReaderWriter::Options* options) - = 0; -}; - -typedef map > PassAttrMap; -PassAttrMap passAttrMap; - -template -struct InstallAttributeBuilder -{ - InstallAttributeBuilder(const string& name) - { - passAttrMap.insert(make_pair(name, new T)); - } -}; - void buildPass(Effect* effect, Technique* tniq, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { @@ -155,9 +148,10 @@ void buildPass(Effect* effect, Technique* tniq, const SGPropertyNode* prop, tniq->passes.push_back(pass); for (int i = 0; i < prop->nChildren(); ++i) { const SGPropertyNode* attrProp = prop->getChild(i); - PassAttrMap::iterator itr = passAttrMap.find(attrProp->getName()); - if (itr != passAttrMap.end()) - itr->second->buildAttribute(effect, pass, attrProp, options); + PassAttributeBuilder* builder + = PassAttributeBuilder::find(attrProp->getNameString()); + if (builder) + builder->buildAttribute(effect, pass, attrProp, options); else SG_LOG(SG_INPUT, SG_ALERT, "skipping unknown pass attribute " << attrProp->getName()); @@ -237,8 +231,10 @@ struct CullFaceBuilder : PassAttributeBuilder const osgDB::ReaderWriter::Options* options) { const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); - if (!realProp) + if (!realProp) { + pass->setMode(GL_CULL_FACE, StateAttribute::OFF); return; + } StateAttributeFactory *attrFact = StateAttributeFactory::instance(); string propVal = realProp->getStringValue(); if (propVal == "front") @@ -247,6 +243,8 @@ struct CullFaceBuilder : PassAttributeBuilder pass->setAttributeAndModes(attrFact->getCullFaceBack()); else if (propVal == "front-back") pass->setAttributeAndModes(new CullFace(CullFace::FRONT_AND_BACK)); + else if (propVal == "off") + pass->setMode(GL_CULL_FACE, StateAttribute::OFF); else SG_LOG(SG_INPUT, SG_ALERT, "invalid cull face property " << propVal); @@ -255,6 +253,15 @@ struct CullFaceBuilder : PassAttributeBuilder InstallAttributeBuilder installCullFace("cull-face"); +EffectNameValue renderingHintInit[] = +{ + { "default", StateSet::DEFAULT_BIN }, + { "opaque", StateSet::OPAQUE_BIN }, + { "transparent", StateSet::TRANSPARENT_BIN } +}; + +EffectPropertyMap renderingHints(renderingHintInit); + struct HintBuilder : public PassAttributeBuilder { void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, @@ -263,11 +270,9 @@ struct HintBuilder : public PassAttributeBuilder const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); if (!realProp) return; - string propVal = realProp->getStringValue(); - if (propVal == "opaque") - pass->setRenderingHint(StateSet::OPAQUE_BIN); - else if (propVal == "transparent") - pass->setRenderingHint(StateSet::TRANSPARENT_BIN); + StateSet::RenderingHint renderingHint = StateSet::DEFAULT_BIN; + findAttr(renderingHints, realProp, renderingHint); + pass->setRenderingHint(renderingHint); } }; @@ -278,6 +283,8 @@ struct RenderBinBuilder : public PassAttributeBuilder void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { + if (!isAttributeActive(effect, prop)) + return; const SGPropertyNode* binProp = prop->getChild("bin-number"); binProp = getEffectPropertyNode(effect, binProp); const SGPropertyNode* nameProp = prop->getChild("bin-name"); @@ -304,7 +311,7 @@ struct MaterialBuilder : public PassAttributeBuilder const osgDB::ReaderWriter::Options* options); }; -EffectNameValue colorModes[] = +EffectNameValue colorModeInit[] = { { "ambient", Material::AMBIENT }, { "ambient-and-diffuse", Material::AMBIENT_AND_DIFFUSE }, @@ -313,11 +320,14 @@ EffectNameValue colorModes[] = { "specular", Material::SPECULAR }, { "off", Material::OFF } }; +EffectPropertyMap colorModes(colorModeInit); void MaterialBuilder::buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { + if (!isAttributeActive(effect, prop)) + return; Material* mat = new Material; const SGPropertyNode* color = 0; if ((color = getEffectPropertyChild(effect, prop, "ambient"))) @@ -361,125 +371,221 @@ void MaterialBuilder::buildAttribute(Effect* effect, Pass* pass, InstallAttributeBuilder installMaterial("material"); +EffectNameValue blendFuncModesInit[] = +{ + {"dst-alpha", BlendFunc::DST_ALPHA}, + {"dst-color", BlendFunc::DST_COLOR}, + {"one", BlendFunc::ONE}, + {"one-minus-dst-alpha", BlendFunc::ONE_MINUS_DST_ALPHA}, + {"one-minus-dst-color", BlendFunc::ONE_MINUS_DST_COLOR}, + {"one-minus-src-alpha", BlendFunc::ONE_MINUS_SRC_ALPHA}, + {"one-minus-src-color", BlendFunc::ONE_MINUS_SRC_COLOR}, + {"src-alpha", BlendFunc::SRC_ALPHA}, + {"src-alpha-saturate", BlendFunc::SRC_ALPHA_SATURATE}, + {"src-color", BlendFunc::SRC_COLOR}, + {"constant-color", BlendFunc::CONSTANT_COLOR}, + {"one-minus-constant-color", BlendFunc::ONE_MINUS_CONSTANT_COLOR}, + {"constant-alpha", BlendFunc::CONSTANT_ALPHA}, + {"one-minus-constant-alpha", BlendFunc::ONE_MINUS_CONSTANT_ALPHA}, + {"zero", BlendFunc::ZERO} +}; +EffectPropertyMap blendFuncModes(blendFuncModesInit); + struct BlendBuilder : public PassAttributeBuilder { void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { + if (!isAttributeActive(effect, prop)) + return; + // XXX Compatibility with early syntax; should go away + // before a release const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); if (!realProp) return; - pass->setMode(GL_BLEND, (realProp->getBoolValue() - ? StateAttribute::ON - : StateAttribute::OFF)); + if (realProp->nChildren() == 0) { + pass->setMode(GL_BLEND, (realProp->getBoolValue() + ? StateAttribute::ON + : StateAttribute::OFF)); + return; + } + + const SGPropertyNode* pmode = getEffectPropertyChild(effect, prop, + "mode"); + // XXX When dynamic parameters are supported, this code should + // create the blend function even if the mode is off. + if (pmode && !pmode->getValue()) { + pass->setMode(GL_BLEND, StateAttribute::OFF); + return; + } + const SGPropertyNode* psource + = getEffectPropertyChild(effect, prop, "source"); + const SGPropertyNode* pdestination + = getEffectPropertyChild(effect, prop, "destination"); + const SGPropertyNode* psourceRGB + = getEffectPropertyChild(effect, prop, "source-rgb"); + const SGPropertyNode* psourceAlpha + = getEffectPropertyChild(effect, prop, "source-alpha"); + const SGPropertyNode* pdestRGB + = getEffectPropertyChild(effect, prop, "destination-rgb"); + const SGPropertyNode* pdestAlpha + = getEffectPropertyChild(effect, prop, "destination-alpha"); + BlendFunc::BlendFuncMode sourceMode = BlendFunc::ONE; + BlendFunc::BlendFuncMode destMode = BlendFunc::ZERO; + if (psource) + findAttr(blendFuncModes, psource, sourceMode); + if (pdestination) + findAttr(blendFuncModes, pdestination, destMode); + if (psource && pdestination + && !(psourceRGB || psourceAlpha || pdestRGB || pdestAlpha) + && sourceMode == BlendFunc::SRC_ALPHA + && destMode == BlendFunc::ONE_MINUS_SRC_ALPHA) { + pass->setAttributeAndModes(StateAttributeFactory::instance() + ->getStandardBlendFunc()); + return; + } + BlendFunc* blendFunc = new BlendFunc; + if (psource) + blendFunc->setSource(sourceMode); + if (pdestination) + blendFunc->setDestination(destMode); + if (psourceRGB) { + BlendFunc::BlendFuncMode sourceRGBMode; + findAttr(blendFuncModes, psourceRGB, sourceRGBMode); + blendFunc->setSourceRGB(sourceRGBMode); + } + if (pdestRGB) { + BlendFunc::BlendFuncMode destRGBMode; + findAttr(blendFuncModes, pdestRGB, destRGBMode); + blendFunc->setDestinationRGB(destRGBMode); + } + if (psourceAlpha) { + BlendFunc::BlendFuncMode sourceAlphaMode; + findAttr(blendFuncModes, psourceAlpha, sourceAlphaMode); + blendFunc->setSourceAlpha(sourceAlphaMode); + } + if (pdestAlpha) { + BlendFunc::BlendFuncMode destAlphaMode; + findAttr(blendFuncModes, pdestAlpha, destAlphaMode); + blendFunc->setDestinationAlpha(destAlphaMode); + } + pass->setAttributeAndModes(blendFunc); } }; InstallAttributeBuilder installBlend("blend"); +EffectNameValue alphaComparisonInit[] = +{ + {"never", AlphaFunc::NEVER}, + {"less", AlphaFunc::LESS}, + {"equal", AlphaFunc::EQUAL}, + {"lequal", AlphaFunc::LEQUAL}, + {"greater", AlphaFunc::GREATER}, + {"notequal", AlphaFunc::NOTEQUAL}, + {"gequal", AlphaFunc::GEQUAL}, + {"always", AlphaFunc::ALWAYS} +}; +EffectPropertyMap +alphaComparison(alphaComparisonInit); + struct AlphaTestBuilder : public PassAttributeBuilder { void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { + if (!isAttributeActive(effect, prop)) + return; + // XXX Compatibility with early syntax; should go away + // before a release const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); if (!realProp) return; - pass->setMode(GL_ALPHA_TEST, (realProp->getBoolValue() - ? StateAttribute::ON - : StateAttribute::OFF)); + if (realProp->nChildren() == 0) { + pass->setMode(GL_ALPHA_TEST, (realProp->getBoolValue() + ? StateAttribute::ON + : StateAttribute::OFF)); + return; + } + + const SGPropertyNode* pmode = getEffectPropertyChild(effect, prop, + "mode"); + // XXX When dynamic parameters are supported, this code should + // create the blend function even if the mode is off. + if (pmode && !pmode->getValue()) { + pass->setMode(GL_ALPHA_TEST, StateAttribute::OFF); + return; + } + const SGPropertyNode* pComp = getEffectPropertyChild(effect, prop, + "comparison"); + const SGPropertyNode* pRef = getEffectPropertyChild(effect, prop, + "reference"); + AlphaFunc::ComparisonFunction func = AlphaFunc::ALWAYS; + float refValue = 1.0f; + if (pComp) + findAttr(alphaComparison, pComp, func); + if (pRef) + refValue = pRef->getValue(); + if (func == AlphaFunc::GREATER && osg::equivalent(refValue, 1.0f)) { + pass->setAttributeAndModes(StateAttributeFactory::instance() + ->getStandardAlphaFunc()); + } else { + AlphaFunc* alphaFunc = new AlphaFunc; + alphaFunc->setFunction(func); + alphaFunc->setReferenceValue(refValue); + pass->setAttributeAndModes(alphaFunc); + } } }; InstallAttributeBuilder installAlphaTest("alpha-test"); -EffectNameValue texEnvModes[] = -{ - {"add", TexEnv::ADD}, - {"blend", TexEnv::BLEND}, - {"decal", TexEnv::DECAL}, - {"modulate", TexEnv::MODULATE}, - {"replace", TexEnv::REPLACE} -}; - -TexEnv* buildTexEnv(Effect* effect, const SGPropertyNode* prop) -{ - const SGPropertyNode* modeProp = getEffectPropertyChild(effect, prop, - "mode"); - const SGPropertyNode* colorProp = getEffectPropertyChild(effect, prop, - "color"); - if (!modeProp) - return 0; - TexEnv::Mode mode = TexEnv::MODULATE; - findAttr(texEnvModes, modeProp, mode); - if (mode == TexEnv::MODULATE) { - return StateAttributeFactory::instance()->getStandardTexEnv(); - } - TexEnv* env = new TexEnv(mode); - if (colorProp) - env->setColor(toOsg(colorProp->getValue())); - return env; - } - - -struct TextureUnitBuilder : PassAttributeBuilder -{ - void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, - const osgDB::ReaderWriter::Options* options); -}; - -void TextureUnitBuilder::buildAttribute(Effect* effect, Pass* pass, - const SGPropertyNode* prop, - const osgDB::ReaderWriter::Options* options) -{ - - // Decode the texture unit - int unit = 0; - const SGPropertyNode* pUnit = prop->getChild("unit"); - if (pUnit) { - unit = pUnit->getValue(); - } else { - const SGPropertyNode* pName = prop->getChild("name"); - if (pName) - try { - unit = boost::lexical_cast(pName->getStringValue()); - } catch (boost::bad_lexical_cast& lex) { - SG_LOG(SG_INPUT, SG_ALERT, "can't decode name as texture unit " - << lex.what()); - } - } - const SGPropertyNode* pType = prop->getChild("type"); - string type; - if (!pType) - type = "2d"; - else - type = pType->getStringValue(); - Texture* texture = 0; - try { - texture = TextureBuilder::buildFromType(effect, type, prop, - options); - } - catch (BuilderException& e) { - SG_LOG(SG_INPUT, SG_ALERT, "No image file for texture, using white "); - texture = StateAttributeFactory::instance()->getWhiteTexture(); - } - pass->setTextureAttributeAndModes(unit, texture); - const SGPropertyNode* envProp = prop->getChild("environment"); - if (envProp) { - TexEnv* env = buildTexEnv(effect, envProp); - if (env) - pass->setTextureAttributeAndModes(unit, env); - } -} - - - InstallAttributeBuilder textureUnitBuilder("texture-unit"); -typedef map > ProgramMap; +// Shader key, used both for shaders with relative and absolute names +typedef pair ShaderKey; + +struct ProgramKey +{ + typedef pair AttribKey; + osgDB::FilePathList paths; + vector shaders; + vector attributes; + struct EqualTo + { + bool operator()(const ProgramKey& lhs, const ProgramKey& rhs) const + { + return (lhs.paths.size() == rhs.paths.size() + && equal(lhs.paths.begin(), lhs.paths.end(), + rhs.paths.begin()) + && lhs.shaders.size() == rhs.shaders.size() + && equal (lhs.shaders.begin(), lhs.shaders.end(), + rhs.shaders.begin()) + && lhs.attributes.size() == rhs.attributes.size() + && equal(lhs.attributes.begin(), lhs.attributes.end(), + rhs.attributes.begin())); + } + }; +}; + +size_t hash_value(const ProgramKey& key) +{ + size_t seed = 0; + boost::hash_range(seed, key.paths.begin(), key.paths.end()); + boost::hash_range(seed, key.shaders.begin(), key.shaders.end()); + boost::hash_range(seed, key.attributes.begin(), key.attributes.end()); + return seed; +} + +// XXX Should these be protected by a mutex? Probably + +typedef tr1::unordered_map, + boost::hash, ProgramKey::EqualTo> +ProgramMap; ProgramMap programMap; -typedef map > ShaderMap; +typedef tr1::unordered_map, boost::hash > +ShaderMap; ShaderMap shaderMap; void reload_shaders() @@ -487,7 +593,7 @@ void reload_shaders() for(ShaderMap::iterator sitr = shaderMap.begin(); sitr != shaderMap.end(); ++sitr) { Shader *shader = sitr->second.get(); - string fileName = osgDB::findDataFile(sitr->first); + string fileName = osgDB::findDataFile(sitr->first.first); if (!fileName.empty()) { shader->loadShaderSourceFromFile(fileName); } @@ -505,32 +611,47 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass, const osgDB::ReaderWriter::Options* options) { + using namespace boost; + if (!isAttributeActive(effect, prop)) + return; PropertyList pVertShaders = prop->getChildren("vertex-shader"); PropertyList pFragShaders = prop->getChildren("fragment-shader"); - string programKey; + PropertyList pAttributes = prop->getChildren("attribute"); + ProgramKey prgKey; for (PropertyList::iterator itr = pVertShaders.begin(), e = pVertShaders.end(); itr != e; ++itr) - { - programKey += (*itr)->getStringValue(); - programKey += ";"; - } + prgKey.shaders.push_back(ShaderKey((*itr)->getStringValue(), + Shader::VERTEX)); for (PropertyList::iterator itr = pFragShaders.begin(), e = pFragShaders.end(); itr != e; ++itr) - { - programKey += (*itr)->getStringValue(); - programKey += ";"; + prgKey.shaders.push_back(ShaderKey((*itr)->getStringValue(), + Shader::FRAGMENT)); + for (PropertyList::iterator itr = pAttributes.begin(), + e = pAttributes.end(); + itr != e; + ++itr) { + const SGPropertyNode* pName = getEffectPropertyChild(effect, *itr, + "name"); + const SGPropertyNode* pIndex = getEffectPropertyChild(effect, *itr, + "index"); + if (!pName || ! pIndex) + throw BuilderException("malformed attribute property"); + prgKey.attributes + .push_back(ProgramKey::AttribKey(pName->getStringValue(), + pIndex->getValue())); } + if (options) + prgKey.paths = options->getDatabasePathList(); Program* program = 0; - ProgramMap::iterator pitr = programMap.find(programKey); + ProgramMap::iterator pitr = programMap.find(prgKey); if (pitr != programMap.end()) { program = pitr->second.get(); } else { program = new Program; - program->setName(programKey); // Add vertex shaders, then fragment shaders PropertyList& pvec = pVertShaders; Shader::Type stype = Shader::VERTEX; @@ -539,31 +660,36 @@ void ShaderProgramBuilder::buildAttribute(Effect* effect, Pass* pass, nameItr != e; ++nameItr) { string shaderName = (*nameItr)->getStringValue(); - ShaderMap::iterator sitr = shaderMap.find(shaderName); + string fileName = osgDB::findDataFile(shaderName, options); + if (fileName.empty()) + throw BuilderException(string("couldn't find shader ") + + shaderName); + ShaderKey skey(fileName, stype); + ShaderMap::iterator sitr = shaderMap.find(skey); if (sitr != shaderMap.end()) { program->addShader(sitr->second.get()); } else { - string fileName = osgDB::findDataFile(shaderName, options); - if (!fileName.empty()) { - ref_ptr shader = new Shader(stype); - if (shader->loadShaderSourceFromFile(fileName)) { - program->addShader(shader.get()); - shaderMap.insert(make_pair(shaderName, shader)); - } + ref_ptr shader = new Shader(stype); + if (shader->loadShaderSourceFromFile(fileName)) { + program->addShader(shader.get()); + shaderMap.insert(ShaderMap::value_type(skey, shader)); } } } pvec = pFragShaders; stype = Shader::FRAGMENT; } - programMap.insert(make_pair(programKey, program)); + BOOST_FOREACH(const ProgramKey::AttribKey& key, prgKey.attributes) { + program->addBindAttribLocation(key.first, key.second); + } + programMap.insert(ProgramMap::value_type(prgKey, program)); } pass->setAttributeAndModes(program); } InstallAttributeBuilder installShaderProgram("program"); -EffectNameValue uniformTypes[] = +EffectNameValue uniformTypesInit[] = { {"float", Uniform::FLOAT}, {"float-vec3", Uniform::FLOAT_VEC3}, @@ -572,12 +698,15 @@ EffectNameValue uniformTypes[] = {"sampler-2d", Uniform::SAMPLER_2D}, {"sampler-3d", Uniform::SAMPLER_3D} }; +EffectPropertyMap uniformTypes(uniformTypesInit); struct UniformBuilder :public PassAttributeBuilder { void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { + if (!isAttributeActive(effect, prop)) + return; const SGPropertyNode* nameProp = prop->getChild("name"); const SGPropertyNode* typeProp = prop->getChild("type"); const SGPropertyNode* valProp @@ -659,18 +788,21 @@ struct NameBuilder : public PassAttributeBuilder InstallAttributeBuilder installName("name"); -EffectNameValue polygonModeModes[] = +EffectNameValue polygonModeModesInit[] = { {"fill", PolygonMode::FILL}, {"line", PolygonMode::LINE}, {"point", PolygonMode::POINT} }; +EffectPropertyMap polygonModeModes(polygonModeModesInit); struct PolygonModeBuilder : public PassAttributeBuilder { void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { + if (!isAttributeActive(effect, prop)) + return; const SGPropertyNode* frontProp = getEffectPropertyChild(effect, prop, "front"); const SGPropertyNode* backProp @@ -691,6 +823,87 @@ struct PolygonModeBuilder : public PassAttributeBuilder }; InstallAttributeBuilder installPolygonMode("polygon-mode"); + +struct VertexProgramTwoSideBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + pass->setMode(GL_VERTEX_PROGRAM_TWO_SIDE, + (realProp->getValue() + ? StateAttribute::ON : StateAttribute::OFF)); + } +}; + +InstallAttributeBuilder +installTwoSide("vertex-program-two-side"); + +struct VertexProgramPointSizeBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + const SGPropertyNode* realProp = getEffectPropertyNode(effect, prop); + if (!realProp) + return; + pass->setMode(GL_VERTEX_PROGRAM_POINT_SIZE, + (realProp->getValue() + ? StateAttribute::ON : StateAttribute::OFF)); + } +}; + +InstallAttributeBuilder +installPointSize("vertex-program-point-size"); + +EffectNameValue depthFunctionInit[] = +{ + {"never", Depth::NEVER}, + {"less", Depth::LESS}, + {"equal", Depth::EQUAL}, + {"lequal", Depth::LEQUAL}, + {"greater", Depth::GREATER}, + {"notequal", Depth::NOTEQUAL}, + {"gequal", Depth::GEQUAL}, + {"always", Depth::ALWAYS} +}; +EffectPropertyMap depthFunction(depthFunctionInit); + +struct DepthBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + { + if (!isAttributeActive(effect, prop)) + return; + ref_ptr depth = new Depth; + const SGPropertyNode* pfunc + = getEffectPropertyChild(effect, prop, "function"); + if (pfunc) { + Depth::Function func = Depth::LESS; + findAttr(depthFunction, pfunc, func); + depth->setFunction(func); + } + const SGPropertyNode* pnear + = getEffectPropertyChild(effect, prop, "near"); + if (pnear) + depth->setZNear(pnear->getValue()); + const SGPropertyNode* pfar + = getEffectPropertyChild(effect, prop, "far"); + if (pfar) + depth->setZFar(pnear->getValue()); + const SGPropertyNode* pmask + = getEffectPropertyChild(effect, prop, "write-mask"); + if (pmask) + depth->setWriteMask(pmask->getValue()); + pass->setAttribute(depth.get()); + } +}; + +InstallAttributeBuilder installDepth("depth"); + void buildTechnique(Effect* effect, const SGPropertyNode* prop, const osgDB::ReaderWriter::Options* options) { @@ -728,18 +941,130 @@ void buildTechnique(Effect* effect, const SGPropertyNode* prop, } } +// Specifically for .ac files... +bool makeParametersFromStateSet(SGPropertyNode* effectRoot, const StateSet* ss) +{ + SGPropertyNode* paramRoot = makeChild(effectRoot, "parameters"); + SGPropertyNode* matNode = paramRoot->getChild("material", 0, true); + Vec4f ambVal, difVal, specVal, emisVal; + float shininess = 0.0f; + const Material* mat = getStateAttribute(ss); + if (mat) { + ambVal = mat->getAmbient(Material::FRONT_AND_BACK); + difVal = mat->getDiffuse(Material::FRONT_AND_BACK); + specVal = mat->getSpecular(Material::FRONT_AND_BACK); + emisVal = mat->getEmission(Material::FRONT_AND_BACK); + shininess = mat->getShininess(Material::FRONT_AND_BACK); + makeChild(matNode, "active")->setValue(true); + makeChild(matNode, "ambient")->setValue(toVec4d(toSG(ambVal))); + makeChild(matNode, "diffuse")->setValue(toVec4d(toSG(difVal))); + makeChild(matNode, "specular")->setValue(toVec4d(toSG(specVal))); + makeChild(matNode, "emissive")->setValue(toVec4d(toSG(emisVal))); + makeChild(matNode, "shininess")->setValue(shininess); + matNode->getChild("color-mode", 0, true)->setStringValue("diffuse"); + } else { + makeChild(matNode, "active")->setValue(false); + } + const ShadeModel* sm = getStateAttribute(ss); + string shadeModelString("smooth"); + if (sm) { + ShadeModel::Mode smMode = sm->getMode(); + if (smMode == ShadeModel::FLAT) + shadeModelString = "flat"; + } + makeChild(paramRoot, "shade-model")->setStringValue(shadeModelString); + string cullFaceString("off"); + const CullFace* cullFace = getStateAttribute(ss); + if (cullFace) { + switch (cullFace->getMode()) { + case CullFace::FRONT: + cullFaceString = "front"; + break; + case CullFace::BACK: + cullFaceString = "back"; + break; + case CullFace::FRONT_AND_BACK: + cullFaceString = "front-back"; + break; + default: + break; + } + } + makeChild(paramRoot, "cull-face")->setStringValue(cullFaceString); + const BlendFunc* blendFunc = getStateAttribute(ss); + SGPropertyNode* blendNode = makeChild(paramRoot, "blend"); + if (blendFunc) { + string sourceMode = findName(blendFuncModes, blendFunc->getSource()); + string destMode = findName(blendFuncModes, blendFunc->getDestination()); + makeChild(blendNode, "active")->setValue(true); + makeChild(blendNode, "source")->setStringValue(sourceMode); + makeChild(blendNode, "destination")->setStringValue(destMode); + makeChild(blendNode, "mode")->setValue(true); + } else { + makeChild(blendNode, "active")->setValue(false); + } + string renderingHint = findName(renderingHints, ss->getRenderingHint()); + makeChild(paramRoot, "rendering-hint")->setStringValue(renderingHint); + makeTextureParameters(paramRoot, ss); + return true; +} + // Walk the techniques property tree, building techniques and // passes. bool Effect::realizeTechniques(const osgDB::ReaderWriter::Options* options) { + if (_isRealized) + return true; PropertyList tniqList = root->getChildren("technique"); for (PropertyList::iterator itr = tniqList.begin(), e = tniqList.end(); itr != e; ++itr) buildTechnique(this, *itr, options); + _isRealized = true; return true; } +void Effect::InitializeCallback::doUpdate(osg::Node* node, osg::NodeVisitor* nv) +{ + EffectGeode* eg = dynamic_cast(node); + if (!eg) + return; + Effect* effect = eg->getEffect(); + if (!effect) + return; + SGPropertyNode* root = getPropertyRoot(); + for (vector >::iterator itr = effect->_extraData.begin(), + end = effect->_extraData.end(); + itr != end; + ++itr) { + InitializeWhenAdded* adder + = dynamic_cast(itr->ptr()); + if (adder) + adder->initOnAdd(effect, root); + } +} + +bool Effect::Key::EqualTo::operator()(const Effect::Key& lhs, + const Effect::Key& rhs) const +{ + if (lhs.paths.size() != rhs.paths.size() + || !equal(lhs.paths.begin(), lhs.paths.end(), rhs.paths.begin())) + return false; + if (lhs.unmerged.valid() && rhs.unmerged.valid()) + return props::Compare()(lhs.unmerged, rhs.unmerged); + else + return lhs.unmerged == rhs.unmerged; +} + +size_t hash_value(const Effect::Key& key) +{ + size_t seed = 0; + if (key.unmerged.valid()) + boost::hash_combine(seed, *key.unmerged); + boost::hash_range(seed, key.paths.begin(), key.paths.end()); + return seed; +} + bool Effect_writeLocalData(const Object& obj, osgDB::Output& fw) { const Effect& effect = static_cast(obj); @@ -762,4 +1087,49 @@ osgDB::RegisterDotOsgWrapperProxy effectProxy &Effect_writeLocalData ); } + +// Property expressions for technique predicates +class PropertyExpression : public SGExpression +{ +public: + PropertyExpression(SGPropertyNode* pnode) : _pnode(pnode) {} + + void eval(bool& value, const expression::Binding*) const + { + value = _pnode->getValue(); + } +protected: + SGPropertyNode_ptr _pnode; +}; + +class EffectPropertyListener : public SGPropertyChangeListener +{ +public: + EffectPropertyListener(Technique* tniq) : _tniq(tniq) {} + + void valueChanged(SGPropertyNode* node) + { + _tniq->refreshValidity(); + } +protected: + osg::ref_ptr _tniq; +}; + +Expression* propertyExpressionParser(const SGPropertyNode* exp, + expression::Parser* parser) +{ + SGPropertyNode_ptr pnode = getPropertyRoot()->getNode(exp->getStringValue(), + true); + PropertyExpression* pexp = new PropertyExpression(pnode); + TechniquePredParser* predParser + = dynamic_cast(parser); + if (predParser) + pnode->addChangeListener(new EffectPropertyListener(predParser + ->getTechnique())); + return pexp; +} + +expression::ExpParserRegistrar propertyRegistrar("property", + propertyExpressionParser); + } diff --git a/simgear/scene/material/Effect.hxx b/simgear/scene/material/Effect.hxx index 2a3c896e..1f33b9e8 100644 --- a/simgear/scene/material/Effect.hxx +++ b/simgear/scene/material/Effect.hxx @@ -19,11 +19,15 @@ #include #include +#include + +#include #include #include #include +#include namespace osg { @@ -40,6 +44,32 @@ class CullVisitor; namespace simgear { class Technique; +class Effect; + +/** + * Object to be initialized at some point after an effect -- and its + * containing effect geode -- are hooked into the scene graph. Some + * things, like manipulations of the global property tree, are are + * only safe in the update process. + */ + +class InitializeWhenAdded +{ +public: + InitializeWhenAdded() : _initialized(false) {}; + virtual ~InitializeWhenAdded() {}; + void initOnAdd(Effect* effect, SGPropertyNode* propRoot) + { + if (!_initialized) { + initOnAddImpl(effect, propRoot); + _initialized = true; + } + } + bool getInitialized() const { return _initialized; } +private: + virtual void initOnAddImpl(Effect* effect, SGPropertyNode* propRoot) = 0; + bool _initialized; +}; class Effect : public osg::Object { @@ -56,14 +86,71 @@ public: Technique* chooseTechnique(osg::RenderInfo* renderInfo); virtual void resizeGLObjectBuffers(unsigned int maxSize); virtual void releaseGLObjects(osg::State* state = 0) const; - /* + /** * Build the techniques from the effect properties. */ bool realizeTechniques(const osgDB::ReaderWriter::Options* options = 0); - + /** + * Updaters that should be derefed when the effect is + * deleted. Updaters arrange to be run by listening on properties + * or something. + */ + struct Updater : public virtual SGReferenced + { + virtual ~Updater() {} + }; + void addUpdater(Updater* data) { _extraData.push_back(data); } + // Callback that is added to the effect geode to initialize the + // effect. + friend struct InitializeCallback; + struct InitializeCallback : public UpdateOnceCallback + { + void doUpdate(osg::Node* node, osg::NodeVisitor* nv); + }; protected: + std::vector > _extraData; ~Effect(); + // Support for a cache of effects that inherit from this one, so + // Effect objects with the same parameters and techniques can be + // shared. + struct Key + { + Key() {} + Key(SGPropertyNode* unmerged_, const osgDB::FilePathList& paths_) + : unmerged(unmerged_), paths(paths_) + { + } + Key& operator=(const Key& rhs) + { + unmerged = rhs.unmerged; + paths = rhs.paths; + return *this; + } + SGPropertyNode_ptr unmerged; + osgDB::FilePathList paths; + struct EqualTo + { + bool operator()(const Key& lhs, const Key& rhs) const; + }; + }; + typedef std::tr1::unordered_map, + boost::hash, Key::EqualTo> Cache; + Cache* getCache() + { + if (!_cache) + _cache = new Cache; + return _cache; + } + Cache* _cache; + friend size_t hash_value(const Key& key); + friend Effect* makeEffect(SGPropertyNode* prop, bool realizeTechniques, + const osgDB::ReaderWriter::Options* options); + bool _isRealized; }; +// Automatic support for boost hash function +size_t hash_value(const Effect::Key&); + + Effect* makeEffect(const std::string& name, bool realizeTechniques, const osgDB::ReaderWriter::Options* options = 0); @@ -71,5 +158,18 @@ Effect* makeEffect(const std::string& name, Effect* makeEffect(SGPropertyNode* prop, bool realizeTechniques, const osgDB::ReaderWriter::Options* options = 0); + +bool makeParametersFromStateSet(SGPropertyNode* paramRoot, + const osg::StateSet* ss); + +namespace effect +{ +/** + * The function that implements effect property tree inheritance. + */ +void mergePropertyTrees(SGPropertyNode* resultNode, + const SGPropertyNode* left, + const SGPropertyNode* right); +} } #endif diff --git a/simgear/scene/material/EffectBuilder.cxx b/simgear/scene/material/EffectBuilder.cxx index 32168a9c..34f14ac8 100644 --- a/simgear/scene/material/EffectBuilder.cxx +++ b/simgear/scene/material/EffectBuilder.cxx @@ -2,6 +2,8 @@ # include #endif +#include + #include #include "EffectBuilder.hxx" @@ -39,6 +41,18 @@ const SGPropertyNode* getEffectPropertyChild(Effect* effect, return getEffectPropertyNode(effect, child); } +string getGlobalProperty(const SGPropertyNode* prop) +{ + if (!prop) + return string(); + const SGPropertyNode* useProp = prop->getChild("use"); + if (!useProp) + return string(); + return useProp->getStringValue(); +} + +namespace effect +{ BuilderException::BuilderException() { } @@ -56,5 +70,19 @@ BuilderException::BuilderException(const std::string& message, BuilderException::~BuilderException() throw() { + +} +} + +bool isAttributeActive(Effect* effect, const SGPropertyNode* prop) +{ + const SGPropertyNode* activeProp + = getEffectPropertyChild(effect, prop, "active"); + return !activeProp || activeProp->getValue(); +} + +namespace effect +{ +const char* colorFields[] = {"red", "green", "blue", "alpha"}; } } diff --git a/simgear/scene/material/EffectBuilder.hxx b/simgear/scene/material/EffectBuilder.hxx index 831c9fab..833ff515 100644 --- a/simgear/scene/material/EffectBuilder.hxx +++ b/simgear/scene/material/EffectBuilder.hxx @@ -18,17 +18,26 @@ #define SIMGEAR_EFFECTBUILDER_HXX 1 #include +#include #include #include #include #include +#include +#include +#include +#include + +#include +#include #include #include #include #include +#include "Effect.hxx" /** * Support classes for parsing effects. */ @@ -36,6 +45,7 @@ namespace simgear { class Effect; +class Pass; /** * Builder that returns an object, probably an OSG object. @@ -79,55 +89,134 @@ protected: } }; -// Simple tables of strings and constants. The table intialization -// *must* be in alphabetical order. +// Tables of strings and constants. We want to reconstruct the effect +// property tree from OSG state sets, so the tables should be bi-directional. + +// two-way map for building StateSets from property descriptions, and +// vice versa. Mostly copied from the boost documentation. + +namespace effect +{ +using boost::multi_index_container; +using namespace boost::multi_index; + +// tags for accessing both sides of a bidirectional map + +struct from{}; +struct to{}; + template struct EffectNameValue { // Don't use std::pair because we want to use aggregate intialization. - const char* first; T second; - class Compare - { - private: - static bool compare(const char* lhs, const char* rhs) - { - return std::strcmp(lhs, rhs) < 0; - } - public: - bool operator()(const EffectNameValue& lhs, - const EffectNameValue& rhs) const - { - return compare(lhs.first, rhs.first); - } - bool operator()(const char* lhs, const EffectNameValue& rhs) const - { - return compare(lhs, rhs.first); - } - bool operator()(const EffectNameValue& lhs, const char* rhs) const - { - return compare (lhs.first, rhs); - } - }; }; -template -bool findAttr(const ENV (&attrs)[N], const SGPropertyNode* prop, T& result) +// The class template bidirectional_map wraps the specification +// of a bidirectional map based on multi_index_container. + +template +struct bidirectional_map +{ + typedef std::pair value_type; + + /* A bidirectional map can be simulated as a multi_index_container + * of pairs of (FromType,ToType) with two unique indices, one + * for each member of the pair. + */ + + typedef multi_index_container< + value_type, + indexed_by< + ordered_unique< + tag, member >, + ordered_unique< + tag, member > + > + > type; +}; + +template +struct EffectPropertyMap +{ + typedef typename bidirectional_map::type BMap; + BMap _map; + template + EffectPropertyMap(const EffectNameValue (&attrs)[N]); +}; + +template +template +EffectPropertyMap::EffectPropertyMap(const EffectNameValue (&attrs)[N]) +{ + for (int i = 0; i < N; ++i) + _map.insert(typename BMap::value_type(attrs[i].first, attrs[i].second)); +} + +class BuilderException : public sg_exception +{ +public: + BuilderException(); + BuilderException(const char* message, const char* origin = 0); + BuilderException(const std::string& message, const std::string& = ""); + virtual ~BuilderException() throw(); +}; +} + +template +void findAttr(const effect::EffectPropertyMap& pMap, + const char* name, + T& result) +{ + using namespace effect; + typename EffectPropertyMap::BMap::iterator itr + = pMap._map.get().find(name); + if (itr == pMap._map.end()) { + throw effect::BuilderException(string("findAttr: could not find attribute ") + + string(name)); + } else { + result = itr->second; + } +} + +template +inline void findAttr(const effect::EffectPropertyMap& pMap, + const std::string& name, + T& result) +{ + findAttr(pMap, name.c_str(), result); +} + +template +void findAttr(const effect::EffectPropertyMap& pMap, + const SGPropertyNode* prop, + T& result) { if (!prop) - return false; + throw effect::BuilderException("findAttr: empty property"); const char* name = prop->getStringValue(); if (!name) - return false; - std::pair itrs - = std::equal_range(&attrs[0], &attrs[N], name, typename ENV::Compare()); - if (itrs.first == itrs.second) { - return false; - } else { - result = itrs.first->second; - return true; - } + throw effect::BuilderException("findAttr: no name for lookup"); + findAttr(pMap, name, result); +} + +template +std::string findName(const effect::EffectPropertyMap& pMap, T value) +{ + using namespace effect; + std::string result; + typename EffectPropertyMap::BMap::template index_iterator::type itr + = pMap._map.get().find(value); + if (itr != pMap._map.get().end()) + result = itr->first; + return result; +} + +template +std::string findName(const effect::EffectPropertyMap& pMap, GLenum value) +{ + return findName(pMap, static_cast(value)); } /** @@ -145,13 +234,241 @@ const SGPropertyNode* getEffectPropertyChild(Effect* effect, const SGPropertyNode* prop, const char* name); -class BuilderException : public sg_exception +/** + * Get the name of a node mentioned in a clause from the global property + * tree. + * @return empty if prop doesn't contain a clause; otherwise the + * mentioned node name. + */ +std::string getGlobalProperty(const SGPropertyNode* prop); + +class PassAttributeBuilder : public SGReferenced +{ +protected: + typedef std::map > + PassAttrMap; + + struct PassAttrMapSingleton : public simgear::Singleton + { + PassAttrMap passAttrMap; + }; +public: + virtual void buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) + = 0; + static PassAttributeBuilder* find(const std::string& str) + { + PassAttrMap::iterator itr + = PassAttrMapSingleton::instance()->passAttrMap.find(str); + if (itr == PassAttrMapSingleton::instance()->passAttrMap.end()) + return 0; + else + return itr->second.ptr(); + } + template friend class InstallAttributeBuilder; +}; + +template +struct InstallAttributeBuilder +{ + InstallAttributeBuilder(const string& name) + { + PassAttributeBuilder::PassAttrMapSingleton::instance() + ->passAttrMap.insert(make_pair(name, new T)); + } +}; + +// The description of an attribute may exist in a pass' XML, but a +// derived effect might want to disable the attribute altogether. So, +// some attributes have an "active" property; if it exists and is +// false, the OSG attribute is not built at all. This is different +// from any OSG mode settings that might be around. +bool isAttributeActive(Effect* effect, const SGPropertyNode* prop); + +namespace effect +{ +/** + * Bridge between types stored in properties and what OSG wants. + */ +template struct OSGBridge; + +template +struct OSGBridge : public OSGBridge +{ +}; + +template<> +struct OSGBridge +{ + typedef SGVec3d sg_type; + static osg::Vec3f getOsgType(const SGVec3d& val) { return toOsg(val); } +}; + +template<> +struct OSGBridge +{ + typedef SGVec3d sg_type; + static osg::Vec3d getOsgType(const SGVec3d& val) { return toOsg(val); } +}; + +template<> +struct OSGBridge +{ + typedef SGVec4d sg_type; + static osg::Vec4f getOsgType(const SGVec4d& val) { return toOsg(val); } +}; + +template<> +struct OSGBridge +{ + typedef SGVec4d sg_type; + static osg::Vec4d getOsgType(const SGVec4d& val) { return toOsg(val); } +}; + +template +struct OSGFunctor : public OSGBridge +{ + OSGFunctor(Obj* obj, void (Obj::*func)(const OSGParam&)) + : _obj(obj), _func(func) {} + void operator()(const typename OSGBridge::sg_type& val) const + { + ((_obj.get())->*_func)(this->getOsgType(val)); + } + osg::ref_ptr_obj; + void (Obj::*_func)(const OSGParam&); +}; + +template +class ScalarChangeListener + : public SGPropertyChangeListener, public InitializeWhenAdded, + public Effect::Updater { public: - BuilderException(); - BuilderException(const char* message, const char* origin = 0); - BuilderException(const std::string& message, const std::string& = ""); - virtual ~BuilderException() throw(); + typedef void (ObjType::*setter_type)(const OSGParamType); + ScalarChangeListener(ObjType* obj, setter_type setter, + const std::string& propName) + : _obj(obj), _setter(setter) + { + _propName = new std::string(propName); + } + virtual ~ScalarChangeListener() + { + delete _propName; + _propName = 0; + } + void valueChanged(SGPropertyNode* node) + { + _obj->*setter(node->getValue()); + } + void initOnAddImpl(Effect* effect, SGPropertyNode* propRoot) + { + SGPropertyNode* listenProp = makeNode(propRoot, *_propName); + delete _propName; + _propName = 0; + if (listenProp) + listenProp->addChangeListener(this, true); + } +private: + osg::ref_ptr _obj; + setter_type _setter; + std::string* _propName; }; + +template +class EffectExtendedPropListener : public InitializeWhenAdded, + public Effect::Updater +{ +public: + template + EffectExtendedPropListener(const Func& func, + const std::string& propName, Itr childNamesBegin, + Itr childNamesEnd) + : _func(func) + { + _propName = new std::string(propName); + _childNames = new std::vector(childNamesBegin, + childNamesEnd); + } + virtual ~EffectExtendedPropListener() + { + delete _propName; + delete _childNames; + } + void initOnAddImpl(Effect* effect, SGPropertyNode* propRoot) + { + SGPropertyNode* parent = propRoot->getNode(*_propName, true); + _propListener + = new ExtendedPropListener(parent, _childNames->begin(), + _childNames->end(), + _func, true); + delete _propName; + _propName = 0; + delete _childNames; + _childNames = 0; + } +private: + std::string* _propName; + std::vector* _childNames; + SGSharedPtr > _propListener; + Func _func; +}; + +/** + * Initialize the value and the possible updating of an effect + * attribute. If the value is specified directly, set it. Otherwise, + * use the tag to look at the parameters. Again, if there is a + * value there set it directly. Otherwise, the parameter contains its + * own tag referring to a property in the global property tree; + * install a change listener that will set the attribute when the + * property changes. + */ +template +void +initFromParameters(Effect* effect, const SGPropertyNode* prop, ObjType* obj, + void (ObjType::*setter)(const OSGParamType)) +{ + const SGPropertyNode* valProp = getEffectPropertyNode(effect, prop); + if (!valProp) + return; + if (valProp->nChildren() == 0) { + obj->*setter(valProp->getValue()); + } else { + std::string propName = getGlobalProperty(prop); + ScalarChangeListener* listener + = new ScalarChangeListener(obj, setter, + propName); + effect->addUpdater(listener); + } +} + +template +void +initFromParameters(Effect* effect, const SGPropertyNode* prop, ObjType* obj, + void (ObjType::*setter)(const OSGParamType&), + NameItrType nameItr) +{ + typedef typename OSGBridge::sg_type sg_type; + const SGPropertyNode* valProp = getEffectPropertyNode(effect, prop); + if (!valProp) + return; + if (valProp->nChildren() == 0) { + (obj->*setter)(OSGBridge + ::getOsgType(valProp->getValue())); + } else { + string listenPropName = getGlobalProperty(valProp); + if (listenPropName.empty()) + return; + typedef OSGFunctor Functor; + Effect::Updater* listener + = new EffectExtendedPropListener + (Functor(obj, setter), listenPropName, nameItr, + nameItr + props::NumComponents::num_components); + effect->addUpdater(listener); + } +} + +extern const char* colorFields[]; +} } #endif diff --git a/simgear/scene/material/EffectCullVisitor.cxx b/simgear/scene/material/EffectCullVisitor.cxx index 62952a9f..efac70b7 100644 --- a/simgear/scene/material/EffectCullVisitor.cxx +++ b/simgear/scene/material/EffectCullVisitor.cxx @@ -56,9 +56,11 @@ void EffectCullVisitor::apply(osg::Geode& node) } Effect* effect = eg->getEffect(); Technique* technique = 0; - if (!(effect && (technique = effect->chooseTechnique(&getRenderInfo())))) { + if (!effect) { CullVisitor::apply(node); return; + } else if (!(technique = effect->chooseTechnique(&getRenderInfo()))) { + return; } // push the node's state. osg::StateSet* node_state = node.getStateSet(); diff --git a/simgear/scene/material/EffectGeode.cxx b/simgear/scene/material/EffectGeode.cxx index 799b567c..aff9aebc 100644 --- a/simgear/scene/material/EffectGeode.cxx +++ b/simgear/scene/material/EffectGeode.cxx @@ -38,10 +38,18 @@ EffectGeode::EffectGeode() { } -EffectGeode::EffectGeode(const EffectGeode& rhs, const CopyOp& copyop) : - Geode(rhs, copyop) +EffectGeode::EffectGeode(const EffectGeode& rhs, const osg::CopyOp& copyop) : + Geode(rhs, copyop), + _effect(static_cast(copyop(rhs._effect.get()))) { - _effect = static_cast(rhs._effect->clone(copyop)); +} + +void EffectGeode::setEffect(Effect* effect) +{ + _effect = effect; + if (!_effect) + return; + addUpdateCallback(new Effect::InitializeCallback); } void EffectGeode::resizeGLObjectBuffers(unsigned int maxSize) diff --git a/simgear/scene/material/EffectGeode.hxx b/simgear/scene/material/EffectGeode.hxx index ad35beb9..93c552a3 100644 --- a/simgear/scene/material/EffectGeode.hxx +++ b/simgear/scene/material/EffectGeode.hxx @@ -31,7 +31,7 @@ public: const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); META_Node(simgear,EffectGeode); Effect* getEffect() const { return _effect.get(); } - void setEffect(Effect* effect) { _effect = effect; } + void setEffect(Effect* effect); virtual void resizeGLObjectBuffers(unsigned int maxSize); virtual void releaseGLObjects(osg::State* = 0) const; typedef DrawableList::iterator DrawablesIterator; diff --git a/simgear/scene/material/Technique.cxx b/simgear/scene/material/Technique.cxx index 4c1dbe16..b8093f35 100644 --- a/simgear/scene/material/Technique.cxx +++ b/simgear/scene/material/Technique.cxx @@ -6,7 +6,6 @@ #include "Technique.hxx" #include "Pass.hxx" -#include #include #include @@ -14,6 +13,7 @@ #include #include +#include #include #include @@ -59,16 +59,15 @@ Technique::Technique(bool alwaysValid) Technique::Technique(const Technique& rhs, const osg::CopyOp& copyop) : _contextMap(rhs._contextMap), _alwaysValid(rhs._alwaysValid), - _shadowingStateSet(rhs._shadowingStateSet), + _shadowingStateSet(copyop(rhs._shadowingStateSet)), _validExpression(rhs._validExpression), _contextIdLocation(rhs._contextIdLocation) { - using namespace std; - using namespace boost; - transform(rhs.passes.begin(), rhs.passes.end(), - back_inserter(passes), - bind(simgear::clone_ref, _1, copyop)); - + for (std::vector >::const_iterator itr = rhs.passes.begin(), + end = rhs.passes.end(); + itr != end; + ++itr) + passes.push_back(static_cast(copyop(itr->get()))); } Technique::~Technique() @@ -276,6 +275,38 @@ Expression* extensionSupportedParser(const SGPropertyNode* exp, expression::ExpParserRegistrar extensionSupportedRegistrar("extension-supported", extensionSupportedParser); +class GLShaderLanguageExpression : public GeneralNaryExpression +{ +public: + void eval(float& value, const expression::Binding* b) const + { + value = 0.0f; + int contextId = getOperand(0)->getValue(b); + GL2Extensions* extensions + = GL2Extensions::Get(static_cast(contextId), true); + if (!extensions) + return; + if (!extensions->isGlslSupported()) + return; + value = extensions->getLanguageVersion(); + } +}; + +Expression* shaderLanguageParser(const SGPropertyNode* exp, + expression::Parser* parser) +{ + GLShaderLanguageExpression* slexp = new GLShaderLanguageExpression; + int location = parser->getBindingLayout().addBinding("__contextId", + expression::INT); + VariableExpression* contextExp = new VariableExpression(location); + slexp->addOperand(contextExp); + return slexp; +} + +expression::ExpParserRegistrar shaderLanguageRegistrar("shader-language", + glVersionParser); + + void Technique::setGLExtensionsPred(float glVersion, const std::vector& extensions) { diff --git a/simgear/scene/material/TextureBuilder.cxx b/simgear/scene/material/TextureBuilder.cxx index a6490c92..a924b955 100644 --- a/simgear/scene/material/TextureBuilder.cxx +++ b/simgear/scene/material/TextureBuilder.cxx @@ -20,19 +20,25 @@ #include "TextureBuilder.hxx" +#include "Pass.hxx" + +#include +#include +#include #include #include #include #include #include +#include #include #include #include #include - #include +#include #include "Noise.hxx" @@ -41,6 +47,12 @@ namespace simgear using namespace std; using namespace osg; +using namespace effect; + +TexEnvCombine* buildTexEnvCombine(Effect* effect, + const SGPropertyNode* envProp); +TexGen* buildTexGen(Effect* Effect, const SGPropertyNode* tgenProp); + // Hack to force inclusion of TextureBuilder.cxx in library osg::Texture* TextureBuilder::buildFromType(Effect* effect, const string& type, const SGPropertyNode*props, @@ -54,9 +66,95 @@ typedef boost::tuple TexTuple; +EffectNameValue texEnvModesInit[] = +{ + {"add", TexEnv::ADD}, + {"blend", TexEnv::BLEND}, + {"decal", TexEnv::DECAL}, + {"modulate", TexEnv::MODULATE}, + {"replace", TexEnv::REPLACE} +}; +EffectPropertyMap texEnvModes(texEnvModesInit); + +TexEnv* buildTexEnv(Effect* effect, const SGPropertyNode* prop) +{ + const SGPropertyNode* modeProp = getEffectPropertyChild(effect, prop, + "mode"); + const SGPropertyNode* colorProp = getEffectPropertyChild(effect, prop, + "color"); + if (!modeProp) + return 0; + TexEnv::Mode mode = TexEnv::MODULATE; + findAttr(texEnvModes, modeProp, mode); + if (mode == TexEnv::MODULATE) { + return StateAttributeFactory::instance()->getStandardTexEnv(); + } + TexEnv* env = new TexEnv(mode); + if (colorProp) + env->setColor(toOsg(colorProp->getValue())); + return env; + } + + +void TextureUnitBuilder::buildAttribute(Effect* effect, Pass* pass, + const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options) +{ + if (!isAttributeActive(effect, prop)) + return; + // Decode the texture unit + int unit = 0; + const SGPropertyNode* pUnit = prop->getChild("unit"); + if (pUnit) { + unit = pUnit->getValue(); + } else { + const SGPropertyNode* pName = prop->getChild("name"); + if (pName) + try { + unit = boost::lexical_cast(pName->getStringValue()); + } catch (boost::bad_lexical_cast& lex) { + SG_LOG(SG_INPUT, SG_ALERT, "can't decode name as texture unit " + << lex.what()); + } + } + const SGPropertyNode* pType = getEffectPropertyChild(effect, prop, "type"); + string type; + if (!pType) + type = "2d"; + else + type = pType->getStringValue(); + Texture* texture = 0; + try { + texture = TextureBuilder::buildFromType(effect, type, prop, + options); + } + catch (BuilderException& e) { + SG_LOG(SG_INPUT, SG_ALERT, "No image file for texture, using white "); + texture = StateAttributeFactory::instance()->getWhiteTexture(); + } + pass->setTextureAttributeAndModes(unit, texture); + const SGPropertyNode* envProp = prop->getChild("environment"); + if (envProp) { + TexEnv* env = buildTexEnv(effect, envProp); + if (env) + pass->setTextureAttributeAndModes(unit, env); + } + const SGPropertyNode* combineProp = prop->getChild("texenv-combine"); + TexEnvCombine* combiner = 0; + if (combineProp && ((combiner = buildTexEnvCombine(effect, combineProp)))) + pass->setTextureAttributeAndModes(unit, combiner); + const SGPropertyNode* tgenProp = prop->getChild("texgen"); + TexGen* tgen = 0; + if (tgenProp && (tgen = buildTexGen(effect, tgenProp))) + pass->setTextureAttributeAndModes(unit, tgen); +} + +// InstallAttributeBuilder call is in Effect.cxx to force this file to +// be linked in. + namespace { -EffectNameValue filterModes[] = +EffectNameValue filterModesInit[] = { { "linear", Texture::LINEAR }, { "linear-mipmap-linear", Texture::LINEAR_MIPMAP_LINEAR}, @@ -65,8 +163,9 @@ EffectNameValue filterModes[] = { "nearest-mipmap-linear", Texture::NEAREST_MIPMAP_LINEAR}, { "nearest-mipmap-nearest", Texture::NEAREST_MIPMAP_NEAREST} }; +EffectPropertyMap filterModes(filterModesInit); -EffectNameValue wrapModes[] = +EffectNameValue wrapModesInit[] = { {"clamp", Texture::CLAMP}, {"clamp-to-border", Texture::CLAMP_TO_BORDER}, @@ -74,7 +173,7 @@ EffectNameValue wrapModes[] = {"mirror", Texture::MIRROR}, {"repeat", Texture::REPEAT} }; - +EffectPropertyMap wrapModes(wrapModesInit); TexTuple makeTexTuple(Effect* effect, const SGPropertyNode* props, @@ -82,24 +181,27 @@ TexTuple makeTexTuple(Effect* effect, const SGPropertyNode* props, const string& texType) { Texture::FilterMode minFilter = Texture::LINEAR_MIPMAP_LINEAR; - findAttr(filterModes, getEffectPropertyChild(effect, props, "filter"), - minFilter); + const SGPropertyNode* ep = 0; + if ((ep = getEffectPropertyChild(effect, props, "filter"))) + findAttr(filterModes, ep, minFilter); Texture::FilterMode magFilter = Texture::LINEAR; - findAttr(filterModes, getEffectPropertyChild(effect, props, - "mag-filter"), - magFilter); + if ((ep = getEffectPropertyChild(effect, props, "mag-filter"))) + findAttr(filterModes, ep, magFilter); const SGPropertyNode* pWrapS = getEffectPropertyChild(effect, props, "wrap-s"); Texture::WrapMode sWrap = Texture::CLAMP; - findAttr(wrapModes, pWrapS, sWrap); + if (pWrapS) + findAttr(wrapModes, pWrapS, sWrap); const SGPropertyNode* pWrapT = getEffectPropertyChild(effect, props, "wrap-t"); Texture::WrapMode tWrap = Texture::CLAMP; - findAttr(wrapModes, pWrapT, tWrap); + if (pWrapT) + findAttr(wrapModes, pWrapT, tWrap); const SGPropertyNode* pWrapR = getEffectPropertyChild(effect, props, "wrap-r"); Texture::WrapMode rWrap = Texture::CLAMP; - findAttr(wrapModes, pWrapR, rWrap); + if (pWrapR) + findAttr(wrapModes, pWrapR, rWrap); const SGPropertyNode* pImage = getEffectPropertyChild(effect, props, "image"); string imageName; @@ -198,6 +300,25 @@ namespace TextureBuilder::Registrar installWhite("white", new WhiteTextureBuilder); } +class TransparentTextureBuilder : public TextureBuilder +{ +public: + Texture* build(Effect* effect, const SGPropertyNode*, + const osgDB::ReaderWriter::Options* options); +}; + +Texture* TransparentTextureBuilder::build(Effect* effect, const SGPropertyNode*, + const osgDB::ReaderWriter::Options* options) +{ + return StateAttributeFactory::instance()->getTransparentTexture(); +} + +namespace +{ +TextureBuilder::Registrar installTransparent("transparent", + new TransparentTextureBuilder); +} + osg::Image* make3DNoiseImage(int texSize) { osg::Image* image = new osg::Image; @@ -280,4 +401,228 @@ namespace TextureBuilder::Registrar installNoise("noise", new NoiseBuilder); } +EffectNameValue combineParamInit[] = +{ + {"replace", TexEnvCombine::REPLACE}, + {"modulate", TexEnvCombine::MODULATE}, + {"add", TexEnvCombine::ADD}, + {"add-signed", TexEnvCombine::ADD_SIGNED}, + {"interpolate", TexEnvCombine::INTERPOLATE}, + {"subtract", TexEnvCombine::SUBTRACT}, + {"dot3-rgb", TexEnvCombine::DOT3_RGB}, + {"dot3-rgba", TexEnvCombine::DOT3_RGBA} +}; + +EffectPropertyMap combineParams(combineParamInit); + +EffectNameValue sourceParamInit[] = +{ + {"constant", TexEnvCombine::CONSTANT}, + {"primary_color", TexEnvCombine::PRIMARY_COLOR}, + {"previous", TexEnvCombine::PREVIOUS}, + {"texture", TexEnvCombine::TEXTURE}, + {"texture0", TexEnvCombine::TEXTURE0}, + {"texture1", TexEnvCombine::TEXTURE1}, + {"texture2", TexEnvCombine::TEXTURE2}, + {"texture3", TexEnvCombine::TEXTURE3}, + {"texture4", TexEnvCombine::TEXTURE4}, + {"texture5", TexEnvCombine::TEXTURE5}, + {"texture6", TexEnvCombine::TEXTURE6}, + {"texture7", TexEnvCombine::TEXTURE7} +}; + +EffectPropertyMap sourceParams(sourceParamInit); + +EffectNameValue opParamInit[] = +{ + {"src-color", TexEnvCombine::SRC_COLOR}, + {"one-minus-src-color", TexEnvCombine::ONE_MINUS_SRC_COLOR}, + {"src-alpha", TexEnvCombine::SRC_ALPHA}, + {"one-minus-src-alpha", TexEnvCombine::ONE_MINUS_SRC_ALPHA} +}; + +EffectPropertyMap operandParams(opParamInit); + +TexEnvCombine* buildTexEnvCombine(Effect* effect, const SGPropertyNode* envProp) +{ + if (!isAttributeActive(effect, envProp)) + return 0; + TexEnvCombine* result = new TexEnvCombine; + const SGPropertyNode* p = 0; + if ((p = getEffectPropertyChild(effect, envProp, "combine-rgb"))) { + TexEnvCombine::CombineParam crgb = TexEnvCombine::MODULATE; + findAttr(combineParams, p, crgb); + result->setCombine_RGB(crgb); + } + if ((p = getEffectPropertyChild(effect, envProp, "combine-alpha"))) { + TexEnvCombine::CombineParam calpha = TexEnvCombine::MODULATE; + findAttr(combineParams, p, calpha); + result->setCombine_Alpha(calpha); + } + if ((p = getEffectPropertyChild(effect, envProp, "source0-rgb"))) { + TexEnvCombine::SourceParam source = TexEnvCombine::TEXTURE; + findAttr(sourceParams, p, source); + result->setSource0_RGB(source); + } + if ((p = getEffectPropertyChild(effect, envProp, "source1-rgb"))) { + TexEnvCombine::SourceParam source = TexEnvCombine::PREVIOUS; + findAttr(sourceParams, p, source); + result->setSource1_RGB(source); + } + if ((p = getEffectPropertyChild(effect, envProp, "source2-rgb"))) { + TexEnvCombine::SourceParam source = TexEnvCombine::CONSTANT; + findAttr(sourceParams, p, source); + result->setSource2_RGB(source); + } + if ((p = getEffectPropertyChild(effect, envProp, "source0-alpha"))) { + TexEnvCombine::SourceParam source = TexEnvCombine::TEXTURE; + findAttr(sourceParams, p, source); + result->setSource0_Alpha(source); + } + if ((p = getEffectPropertyChild(effect, envProp, "source1-alpha"))) { + TexEnvCombine::SourceParam source = TexEnvCombine::PREVIOUS; + findAttr(sourceParams, p, source); + result->setSource1_Alpha(source); + } + if ((p = getEffectPropertyChild(effect, envProp, "source2-alpha"))) { + TexEnvCombine::SourceParam source = TexEnvCombine::CONSTANT; + findAttr(sourceParams, p, source); + result->setSource2_Alpha(source); + } + if ((p = getEffectPropertyChild(effect, envProp, "operand0-rgb"))) { + TexEnvCombine::OperandParam op = TexEnvCombine::SRC_COLOR; + findAttr(operandParams, p, op); + result->setOperand0_RGB(op); + } + if ((p = getEffectPropertyChild(effect, envProp, "operand1-rgb"))) { + TexEnvCombine::OperandParam op = TexEnvCombine::SRC_COLOR; + findAttr(operandParams, p, op); + result->setOperand1_RGB(op); + } + if ((p = getEffectPropertyChild(effect, envProp, "operand2-rgb"))) { + TexEnvCombine::OperandParam op = TexEnvCombine::SRC_ALPHA; + findAttr(operandParams, p, op); + result->setOperand2_RGB(op); + } + if ((p = getEffectPropertyChild(effect, envProp, "operand0-alpha"))) { + TexEnvCombine::OperandParam op = TexEnvCombine::SRC_ALPHA; + findAttr(operandParams, p, op); + result->setOperand0_Alpha(op); + } + if ((p = getEffectPropertyChild(effect, envProp, "operand1-alpha"))) { + TexEnvCombine::OperandParam op = TexEnvCombine::SRC_ALPHA; + findAttr(operandParams, p, op); + result->setOperand1_Alpha(op); + } + if ((p = getEffectPropertyChild(effect, envProp, "operand2-alpha"))) { + TexEnvCombine::OperandParam op = TexEnvCombine::SRC_ALPHA; + findAttr(operandParams, p, op); + result->setOperand2_Alpha(op); + } + if ((p = getEffectPropertyChild(effect, envProp, "scale-rgb"))) { + result->setScale_RGB(p->getValue()); + } + if ((p = getEffectPropertyChild(effect, envProp, "scale-alpha"))) { + result->setScale_Alpha(p->getValue()); + } +#if 0 + if ((p = getEffectPropertyChild(effect, envProp, "constant-color"))) { + SGVec4d color = p->getValue(); + result->setConstantColor(toOsg(color)); + } else if ((p = getEffectPropertyChild(effect, envProp, + "light-direction"))) { + SGVec3d direction = p->getValue(); + result->setConstantColorAsLightDirection(toOsg(direction)); + } +#endif + const SGPropertyNode* colorNode = envProp->getChild("constant-color"); + if (colorNode) + initFromParameters(effect, colorNode, result, + &TexEnvCombine::setConstantColor, colorFields); + return result; +} + +EffectNameValue tgenModeInit[] = +{ + { "object-linear", TexGen::OBJECT_LINEAR}, + { "eye-linear", TexGen::EYE_LINEAR}, + { "sphere-map", TexGen::SPHERE_MAP}, + { "normal-map", TexGen::NORMAL_MAP}, + { "reflection-map", TexGen::REFLECTION_MAP} +}; + +EffectPropertyMap tgenModes(tgenModeInit); + +EffectNameValue tgenCoordInit[] = +{ + {"s", TexGen::S}, + {"t", TexGen::T}, + {"r", TexGen::R}, + {"q", TexGen::Q} +}; + +EffectPropertyMap tgenCoords(tgenCoordInit); + +TexGen* buildTexGen(Effect* effect, const SGPropertyNode* tgenProp) +{ + if (!isAttributeActive(effect, tgenProp)) + return 0; + TexGen* result = new TexGen; + const SGPropertyNode* p = 0; + TexGen::Mode mode = TexGen::OBJECT_LINEAR; + findAttr(tgenModes, getEffectPropertyChild(effect, tgenProp, "mode"), mode); + result->setMode(mode); + const SGPropertyNode* planesNode = tgenProp->getChild("planes"); + if (planesNode) { + for (int i = 0; i < planesNode->nChildren(); ++i) { + const SGPropertyNode* planeNode = planesNode->getChild(i); + TexGen::Coord coord; + findAttr(tgenCoords, planeNode->getName(), coord); + const SGPropertyNode* realNode + = getEffectPropertyNode(effect, planeNode); + SGVec4d plane = realNode->getValue(); + result->setPlane(coord, toOsg(plane)); + } + } + return result; +} + +bool makeTextureParameters(SGPropertyNode* paramRoot, const StateSet* ss) +{ + SGPropertyNode* texUnit = makeChild(paramRoot, "texture"); + const Texture* tex = getStateAttribute(0, ss); + const Texture2D* texture = dynamic_cast(tex); + makeChild(texUnit, "unit")->setValue(0); + if (!tex) { + // The default shader-based technique ignores active + makeChild(texUnit, "active")->setValue(false); + return false; + } + const Image* image = texture->getImage(); + string imageName; + if (image) { + imageName = image->getFileName(); + } else { + makeChild(texUnit, "active")->setValue(false); + makeChild(texUnit, "type")->setValue("white"); + return false; + } + makeChild(texUnit, "active")->setValue(true); + makeChild(texUnit, "type")->setValue("2d"); + string filter = findName(filterModes, + texture->getFilter(Texture::MIN_FILTER)); + string magFilter = findName(filterModes, + texture->getFilter(Texture::MAG_FILTER)); + string wrapS = findName(wrapModes, texture->getWrap(Texture::WRAP_S)); + string wrapT = findName(wrapModes, texture->getWrap(Texture::WRAP_T)); + string wrapR = findName(wrapModes, texture->getWrap(Texture::WRAP_R)); + makeChild(texUnit, "image")->setStringValue(imageName); + makeChild(texUnit, "filter")->setStringValue(filter); + makeChild(texUnit, "mag-filter")->setStringValue(magFilter); + makeChild(texUnit, "wrap-s")->setStringValue(wrapS); + makeChild(texUnit, "wrap-t")->setStringValue(wrapT); + makeChild(texUnit, "wrap-r")->setStringValue(wrapR); + return true; +} + } diff --git a/simgear/scene/material/TextureBuilder.hxx b/simgear/scene/material/TextureBuilder.hxx index 259ef614..ca6c5494 100644 --- a/simgear/scene/material/TextureBuilder.hxx +++ b/simgear/scene/material/TextureBuilder.hxx @@ -17,6 +17,7 @@ #ifndef SIMGEAR_TEXTUREBUILDER_HXX #define SIMGEAR_TEXTUREBUILDER_HXX 1 +#include #include #include "EffectBuilder.hxx" @@ -30,5 +31,14 @@ public: const SGPropertyNode*props, const osgDB::ReaderWriter::Options* options); }; + +struct TextureUnitBuilder : public PassAttributeBuilder +{ + void buildAttribute(Effect* effect, Pass* pass, const SGPropertyNode* prop, + const osgDB::ReaderWriter::Options* options); +}; + + +bool makeTextureParameters(SGPropertyNode* paramRoot, const osg::StateSet* ss); } #endif diff --git a/simgear/scene/material/makeEffect.cxx b/simgear/scene/material/makeEffect.cxx index b2d37ff2..1a8efcdb 100644 --- a/simgear/scene/material/makeEffect.cxx +++ b/simgear/scene/material/makeEffect.cxx @@ -4,6 +4,7 @@ #endif #include "Effect.hxx" +#include "EffectBuilder.hxx" #include "Technique.hxx" #include "Pass.hxx" @@ -32,12 +33,14 @@ #include #include #include +#include #include namespace simgear { using namespace std; using namespace osg; +using namespace effect; typedef vector RawPropVector; typedef map > EffectMap; @@ -71,6 +74,8 @@ struct PropPredicate const SGPropertyNode* node; }; +namespace effect +{ void mergePropertyTrees(SGPropertyNode* resultNode, const SGPropertyNode* left, const SGPropertyNode* right) { @@ -107,29 +112,48 @@ void mergePropertyTrees(SGPropertyNode* resultNode, copyProperties(*itr, newChild); } } +} Effect* makeEffect(const string& name, bool realizeTechniques, const osgDB::ReaderWriter::Options* options) { - OpenThreads::ScopedLock lock(effectMutex); - EffectMap::iterator itr = effectMap.find(name); - if (itr != effectMap.end()) - return itr->second.get(); + { + OpenThreads::ScopedLock lock(effectMutex); + EffectMap::iterator itr = effectMap.find(name); + if (itr != effectMap.end()) + return itr->second.get(); + } string effectFileName(name); effectFileName += ".eff"; string absFileName = osgDB::findDataFile(effectFileName, options); if (absFileName.empty()) { - SG_LOG(SG_INPUT, SG_WARN, "can't find \"" << effectFileName << "\""); + SG_LOG(SG_INPUT, SG_ALERT, "can't find \"" << effectFileName << "\""); return 0; } SGPropertyNode_ptr effectProps = new SGPropertyNode(); - readProperties(absFileName, effectProps.ptr(), 0, true); - Effect* result = makeEffect(effectProps.ptr(), realizeTechniques, options); - if (result) - effectMap.insert(make_pair(name, result)); - return result; + try { + readProperties(absFileName, effectProps.ptr(), 0, true); + } + catch (sg_io_exception& e) { + SG_LOG(SG_INPUT, SG_ALERT, "error reading \"" << absFileName << "\": " + << e.getFormattedMessage()); + return 0; + } + ref_ptr result = makeEffect(effectProps.ptr(), realizeTechniques, + options); + if (result.valid()) { + OpenThreads::ScopedLock lock(effectMutex); + pair irslt + = effectMap.insert(make_pair(name, result)); + if (!irslt.second) { + // Another thread beat us to it!. Discard our newly + // constructed Effect and use the one in the cache. + result = irslt.first->second; + } + } + return result.release(); } @@ -160,29 +184,64 @@ Effect* makeEffect(SGPropertyNode* prop, } } } - Effect* effect = new Effect; + ref_ptr effect; // Merge with the parent effect, if any - const SGPropertyNode* inheritProp = prop->getChild("inherits-from"); + SGPropertyNode_ptr inheritProp = prop->getChild("inherits-from"); Effect* parent = 0; if (inheritProp) { - parent = makeEffect(inheritProp->getStringValue(), realizeTechniques, - options); - if(parent) - { - effect->root = new SGPropertyNode; - mergePropertyTrees(effect->root, prop, parent->root); - effect->root->removeChild("inherits-from"); + //prop->removeChild("inherits-from"); + parent = makeEffect(inheritProp->getStringValue(), false, options); + if (parent) { + Effect::Key key; + key.unmerged = prop; + if (options) { + key.paths = options->getDatabasePathList(); + } + Effect::Cache* cache = 0; + Effect::Cache::iterator itr; + { + OpenThreads::ScopedLock + lock(effectMutex); + cache = parent->getCache(); + itr = cache->find(key); + if (itr != cache->end()) + effect = itr->second.get(); + } + if (!effect.valid()) { + effect = new Effect; + effect->root = new SGPropertyNode; + mergePropertyTrees(effect->root, prop, parent->root); + effect->parametersProp = effect->root->getChild("parameters"); + OpenThreads::ScopedLock + lock(effectMutex); + pair irslt + = cache->insert(make_pair(key, effect)); + if (!irslt.second) + effect = irslt.first->second; + } } else { - effect->root = prop; - effect->root->removeChild("inherits-from"); + SG_LOG(SG_INPUT, SG_ALERT, "can't find base effect " << + inheritProp->getStringValue()); + return 0; } } else { + effect = new Effect; effect->root = prop; + effect->parametersProp = effect->root->getChild("parameters"); } - effect->parametersProp = effect->root->getChild("parameters"); - if (realizeTechniques) - effect->realizeTechniques(options); - return effect; + if (realizeTechniques) { + try { + OpenThreads::ScopedLock + lock(effectMutex); + effect->realizeTechniques(options); + } + catch (BuilderException& e) { + SG_LOG(SG_INPUT, SG_ALERT, "Error building technique: " + << e.getFormattedMessage()); + return 0; + } + } + return effect.release(); } } diff --git a/simgear/scene/model/ModelRegistry.cxx b/simgear/scene/model/ModelRegistry.cxx index 3b5b545c..2bf8c878 100644 --- a/simgear/scene/model/ModelRegistry.cxx +++ b/simgear/scene/model/ModelRegistry.cxx @@ -62,9 +62,6 @@ using namespace osgUtil; using namespace osgDB; using namespace simgear; -using OpenThreads::ReentrantMutex; -using OpenThreads::ScopedLock; - // Little helper class that holds an extra reference to a // loaded 3d model. // Since we clone all structural nodes from our 3d models, @@ -128,125 +125,6 @@ protected: } }; -// Change the StateSets of a model to hold different textures based on -// a livery path. - -class TextureUpdateVisitor : public NodeAndDrawableVisitor { -public: - TextureUpdateVisitor(const FilePathList& pathList) : - NodeAndDrawableVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN), - _pathList(pathList) - { - } - - virtual void apply(Node& node) - { - StateSet* stateSet = cloneStateSet(node.getStateSet()); - if (stateSet) - node.setStateSet(stateSet); - traverse(node); - } - - virtual void apply(Drawable& drawable) - { - StateSet* stateSet = cloneStateSet(drawable.getStateSet()); - if (stateSet) - drawable.setStateSet(stateSet); - } - // Copied from Mathias' earlier SGTextureUpdateVisitor -protected: - Texture2D* textureReplace(int unit, const StateAttribute* attr) - { - const Texture2D* texture = dynamic_cast(attr); - - if (!texture) - return 0; - - const Image* image = texture->getImage(); - const string* fullFilePath = 0; - if (image) { - // The currently loaded file name - fullFilePath = &image->getFileName(); - - } else { - fullFilePath = &texture->getName(); - } - // The short name - string fileName = getSimpleFileName(*fullFilePath); - if (fileName.empty()) - return 0; - // The name that should be found with the current database path - string fullLiveryFile = findFileInPath(fileName, _pathList); - // If it is empty or they are identical then there is nothing to do - if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath) - return 0; - Image* newImage = readImageFile(fullLiveryFile); - if (!newImage) - return 0; - CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES); - Texture2D* newTexture = static_cast(copyOp(texture)); - if (!newTexture) { - return 0; - } else { - newTexture->setImage(newImage); - return newTexture; - } - } - - StateSet* cloneStateSet(const StateSet* stateSet) - { - typedef pair Tex2D; - vector newTextures; - StateSet* result = 0; - - if (!stateSet) - return 0; - int numUnits = stateSet->getTextureAttributeList().size(); - if (numUnits > 0) { - for (int i = 0; i < numUnits; ++i) { - const StateAttribute* attr - = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE); - Texture2D* newTexture = textureReplace(i, attr); - if (newTexture) - newTextures.push_back(Tex2D(i, newTexture)); - } - if (!newTextures.empty()) { - result = static_cast(stateSet->clone(CopyOp())); - for (vector::iterator i = newTextures.begin(); - i != newTextures.end(); - ++i) { - result->setTextureAttribute(i->first, i->second); - } - } - } - return result; - } -private: - FilePathList _pathList; -}; - -// Create new userdata structs in a copied model. -// The BVH trees are shared with the original model, but the velocity fields -// should usually be distinct fields for distinct models. -class UserDataCopyVisitor : public osg::NodeVisitor { -public: - UserDataCopyVisitor() : - osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR, - osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) - { - } - virtual void apply(osg::Node& node) - { - osg::ref_ptr userData; - userData = SGSceneUserData::getSceneUserData(&node); - if (userData.valid()) { - SGSceneUserData* newUserData = new SGSceneUserData(*userData); - newUserData->setVelocity(0); - node.setUserData(newUserData); - } - node.traverse(*this); - } -}; class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor { public: @@ -324,10 +202,8 @@ ReaderWriter::ReadResult ModelRegistry::readImage(const string& fileName, const ReaderWriter::Options* opt) { - ScopedLock lock(readerMutex); CallbackMap::iterator iter = imageCallbackMap.find(getFileExtension(fileName)); - // XXX Workaround for OSG plugin bug { if (iter != imageCallbackMap.end() && iter->second.valid()) return iter->second->readImage(fileName, opt); @@ -416,41 +292,6 @@ osg::Node* OptimizeModelPolicy::optimize(osg::Node* node, return node; } -osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName, - const osgDB::ReaderWriter::Options* opt) -{ - // Add an extra reference to the model stored in the database. - // That is to avoid expiring the object from the cache even if it is still - // in use. Note that the object cache will think that a model is unused - // if the reference count is 1. If we clone all structural nodes here - // we need that extra reference to the original object - SGDatabaseReference* databaseReference; - databaseReference = new SGDatabaseReference(model); - CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL; - flags &= ~CopyOp::DEEP_COPY_TEXTURES; - flags &= ~CopyOp::DEEP_COPY_IMAGES; - flags &= ~CopyOp::DEEP_COPY_STATESETS; - flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES; - flags &= ~CopyOp::DEEP_COPY_ARRAYS; - flags &= ~CopyOp::DEEP_COPY_PRIMITIVES; - // This will safe display lists ... - flags &= ~CopyOp::DEEP_COPY_DRAWABLES; - flags &= ~CopyOp::DEEP_COPY_SHAPES; - osg::Node* res = CopyOp(flags)(model); - res->addObserver(databaseReference); - - // Update liveries - TextureUpdateVisitor liveryUpdate(opt->getDatabasePathList()); - res->accept(liveryUpdate); - - // Copy the userdata fields, still sharing the boundingvolumes, - // but introducing new data for velocities. - UserDataCopyVisitor userDataCopyVisitor; - res->accept(userDataCopyVisitor); - - return res; -} - string OSGSubstitutePolicy::substitute(const string& name, const ReaderWriter::Options* opt) { @@ -509,10 +350,6 @@ ReaderWriter::ReadResult ModelRegistry::readNode(const string& fileName, const ReaderWriter::Options* opt) { - ScopedLock lock(readerMutex); - - // XXX Workaround for OSG plugin bug. -// Registry* registry = Registry::instance(); ReaderWriter::ReadResult res; CallbackMap::iterator iter = nodeCallbackMap.find(getFileExtension(fileName)); @@ -599,7 +436,7 @@ struct ACProcessPolicy { }; typedef ModelRegistryCallback ACCallback; diff --git a/simgear/scene/model/ModelRegistry.hxx b/simgear/scene/model/ModelRegistry.hxx index 909c9460..94931bca 100644 --- a/simgear/scene/model/ModelRegistry.hxx +++ b/simgear/scene/model/ModelRegistry.hxx @@ -19,8 +19,6 @@ #ifndef _SG_MODELREGISTRY_HXX #define _SG_MODELREGISTRY_HXX 1 -#include - #include #include #include @@ -61,12 +59,12 @@ namespace simgear // readNode function is specified as a template with a bunch of // pluggable (and predefined) policies. template + typename SubstitutePolicy, typename BVHPolicy> class ModelRegistryCallback : public osgDB::Registry::ReadFileCallback { public: ModelRegistryCallback(const std::string& extension) : _processPolicy(extension), _cachePolicy(extension), - _optimizePolicy(extension), _copyPolicy(extension), + _optimizePolicy(extension), _substitutePolicy(extension), _bvhPolicy(extension) { } @@ -100,9 +98,7 @@ public: _bvhPolicy.buildBVH(fileName, optimizedNode.get()); _cachePolicy.addToCache(fileName, optimizedNode.get()); } - osg::ref_ptr copyNode; - copyNode = _copyPolicy.copy(optimizedNode.get(), fileName, opt); - return ReaderWriter::ReadResult(copyNode); + return ReaderWriter::ReadResult(optimizedNode); } protected: static osgDB::ReaderWriter::ReadResult @@ -120,7 +116,6 @@ protected: ProcessPolicy _processPolicy; CachePolicy _cachePolicy; OptimizePolicy _optimizePolicy; - CopyPolicy _copyPolicy; SubstitutePolicy _substitutePolicy; BVHPolicy _bvhPolicy; virtual ~ModelRegistryCallback() {} @@ -169,21 +164,6 @@ struct NoOptimizePolicy { } }; -struct DefaultCopyPolicy { - DefaultCopyPolicy(const std::string& extension) {} - osg::Node* copy(osg::Node* node, const std::string& fileName, - const osgDB::ReaderWriter::Options* opt); -}; - -struct NoCopyPolicy { - NoCopyPolicy(const std::string& extension) {} - osg::Node* copy(osg::Node* node, const std::string& fileName, - const osgDB::ReaderWriter::Options* opt) - { - return node; - } -}; - struct OSGSubstitutePolicy { OSGSubstitutePolicy(const std::string& extension) {} std::string substitute(const std::string& name, @@ -215,7 +195,7 @@ struct NoBuildBVHPolicy { }; typedef ModelRegistryCallback DefaultCallback; @@ -243,15 +223,12 @@ protected: CallbackMap imageCallbackMap; CallbackMap nodeCallbackMap; osg::ref_ptr _defaultCallback; - // Protect against simultaneous calls from main thread (MP models) - // and pager thread. - OpenThreads::ReentrantMutex readerMutex; }; // Callback that only loads the file without any caching or // postprocessing. typedef ModelRegistryCallback LoadOnlyCallback; diff --git a/simgear/scene/model/SGMaterialAnimation.cxx b/simgear/scene/model/SGMaterialAnimation.cxx index ca9ac364..d726a59c 100644 --- a/simgear/scene/model/SGMaterialAnimation.cxx +++ b/simgear/scene/model/SGMaterialAnimation.cxx @@ -22,8 +22,14 @@ #include #include +#include +#include +#include +#include #include +using namespace std; + namespace { /** * Get a color from properties. @@ -209,7 +215,25 @@ public: virtual void apply(osg::Geode& node) { - maybeGetMaterialValues(node.getStateSet()); + using namespace simgear; + EffectGeode* eg = dynamic_cast(&node); + if (eg) { + const Effect* effect = eg->getEffect(); + if (effect) + for (vector >::const_iterator itr + = effect->techniques.begin(), end = effect->techniques.end(); + itr != end; + ++itr) { + const Technique* tniq = itr->get(); + for (vector >::const_iterator pitr + = tniq->passes.begin(), pend = tniq->passes.end(); + pitr != pend; + ++pitr) + maybeGetMaterialValues(pitr->get()); + } + } else { + maybeGetMaterialValues(node.getStateSet()); + } int numDrawables = node.getNumDrawables(); for (int i = 0; i < numDrawables; i++) { osg::Geometry* geom = dynamic_cast(node.getDrawable(i)); @@ -230,18 +254,20 @@ public: } } - void maybeGetMaterialValues(osg::StateSet* stateSet) + void maybeGetMaterialValues(const osg::StateSet* stateSet) { if (!stateSet) return; - osg::Material* nodeMat - = dynamic_cast(stateSet->getAttribute(osg::StateAttribute::MATERIAL)); + const osg::Material* nodeMat + = dynamic_cast(stateSet + ->getAttribute(osg::StateAttribute + ::MATERIAL)); if (!nodeMat) return; material = nodeMat; } - osg::ref_ptr material; + osg::ref_ptr material; osg::Vec4 ambientDiffuse; }; diff --git a/simgear/scene/model/SGPagedLOD.cxx b/simgear/scene/model/SGPagedLOD.cxx index ddd6dabd..faaf42bb 100644 --- a/simgear/scene/model/SGPagedLOD.cxx +++ b/simgear/scene/model/SGPagedLOD.cxx @@ -19,6 +19,8 @@ #endif #include +#include +#include #include #include @@ -70,3 +72,19 @@ void SGPagedLOD::forceLoad(osgDB::DatabasePager *dbp) _readerWriterOptions.get()); } +bool SGPagedLOD_writeLocalData(const Object& obj, osgDB::Output& fw) +{ + return true; +} + +namespace +{ +osgDB::RegisterDotOsgWrapperProxy sgPagedLODProxy +( + new SGPagedLOD, + "simgear::SGPagedLOD", + "Object Node LOD PagedLOD SGPagedLOD Group", + 0, + &SGPagedLOD_writeLocalData + ); +} diff --git a/simgear/scene/model/SGReaderWriterXML.cxx b/simgear/scene/model/SGReaderWriterXML.cxx index 31c0c56d..a097b809 100644 --- a/simgear/scene/model/SGReaderWriterXML.cxx +++ b/simgear/scene/model/SGReaderWriterXML.cxx @@ -20,6 +20,13 @@ # include #endif +#include +//yuck +#include + +#include + +#include #include #include #include @@ -43,7 +50,9 @@ #include "model.hxx" #include "SGText.hxx" +using namespace std; using namespace simgear; +using namespace osg; static osg::Node * sgLoad3DModel_internal(const std::string& path, @@ -105,6 +114,71 @@ private: SGSharedPtr mCondition; }; + +// Little helper class that holds an extra reference to a +// loaded 3d model. +// Since we clone all structural nodes from our 3d models, +// the database pager will only see one single reference to +// top node of the model and expire it relatively fast. +// We attach that extra reference to every model cloned from +// a base model in the pager. When that cloned model is deleted +// this extra reference is deleted too. So if there are no +// cloned models left the model will expire. +namespace { +class SGDatabaseReference : public osg::Observer +{ +public: + SGDatabaseReference(osg::Referenced* referenced) : + mReferenced(referenced) + { } + virtual void objectDeleted(void*) + { + mReferenced = 0; + } +private: + osg::ref_ptr mReferenced; +}; + +void makeEffectAnimations(PropertyList& animation_nodes, + PropertyList& effect_nodes) +{ + for (PropertyList::iterator itr = animation_nodes.begin(); + itr != animation_nodes.end(); + ++itr) { + SGPropertyNode* animProp = itr->ptr(); + SGPropertyNode* typeProp = animProp->getChild("type"); + if (!typeProp || strcmp(typeProp->getStringValue(), "shader")) + continue; + SGPropertyNode* shaderProp = animProp->getChild("shader"); + if (!shaderProp || strcmp(shaderProp->getStringValue(), "chrome")) + continue; + *itr = 0; + SGPropertyNode* textureProp = animProp->getChild("texture"); + if (!textureProp) + continue; + SGPropertyNode_ptr effectProp = new SGPropertyNode(); + makeChild(effectProp.ptr(), "inherits-from") + ->setValue("Effects/chrome"); + SGPropertyNode* paramsProp = makeChild(effectProp.get(), "parameters"); + makeChild(paramsProp, "chrome-texture") + ->setValue(textureProp->getStringValue()); + PropertyList objectNameNodes = animProp->getChildren("object-name"); + for (PropertyList::iterator objItr = objectNameNodes.begin(), + end = objectNameNodes.end(); + objItr != end; + ++objItr) + effectProp->addChild("object-name") + ->setStringValue((*objItr)->getStringValue()); + effect_nodes.push_back(effectProp); + } + animation_nodes.erase(remove_if(animation_nodes.begin(), + animation_nodes.end(), + !boost::bind(&SGPropertyNode_ptr::valid, + _1)), + animation_nodes.end()); +} +} + static osg::Node * sgLoad3DModel_internal(const string &path, const osgDB::ReaderWriter::Options* options_, @@ -167,6 +241,9 @@ sgLoad3DModel_internal(const string &path, SGPropertyNode *mp = props->getNode("multiplay"); if (mp && prop_root && prop_root->getParent()) copyProperties(mp, prop_root); + } else { + SG_LOG(SG_INPUT, SG_DEBUG, "model without wrapper: " + << modelpath.str()); } osg::ref_ptr options @@ -181,10 +258,31 @@ sgLoad3DModel_internal(const string &path, texturepath = texturepath.dir(); options->setDatabasePath(texturepath.str()); - model = osgDB::readNodeFile(modelpath.str(), options.get()); - if (model == 0) + osgDB::ReaderWriter::ReadResult modelResult + = osgDB::Registry::instance()->readNode(modelpath.str(), + options.get()); + if (!modelResult.validNode()) throw sg_io_exception("Failed to load 3D model", sg_location(modelpath.str())); + model = copyModel(modelResult.getNode()); + // Add an extra reference to the model stored in the database. + // That is to avoid expiring the object from the cache even if + // it is still in use. Note that the object cache will think + // that a model is unused if the reference count is 1. If we + // clone all structural nodes here we need that extra + // reference to the original object + SGDatabaseReference* databaseReference; + databaseReference = new SGDatabaseReference(modelResult.getNode()); + model->addObserver(databaseReference); + + // Update liveries + TextureUpdateVisitor liveryUpdate(options->getDatabasePathList()); + model->accept(liveryUpdate); + + // Copy the userdata fields, still sharing the boundingvolumes, + // but introducing new data for velocities. + UserDataCopyVisitor userDataCopyVisitor; + model->accept(userDataCopyVisitor); } model->setName(modelpath.str()); @@ -224,7 +322,8 @@ sgLoad3DModel_internal(const string &path, SGPath submodelpath; osg::ref_ptr submodel; string submodelFileName = sub_props->getStringValue("path"); - if ( submodelFileName.size() > 2 && submodelFileName.substr( 0, 2 ) == "./" ) { + if (submodelFileName.size() > 2 + && !submodelFileName.compare(0, 2, "./" )) { submodelpath = modelpath.dir(); submodelpath.append( submodelFileName.substr( 2 ) ); } else { @@ -242,7 +341,7 @@ sgLoad3DModel_internal(const string &path, throw; } - osg::ref_ptr submodel_final=submodel.get(); + osg::ref_ptr submodel_final = submodel; SGPropertyNode *offs = sub_props->getNode("offsets", false); if (offs) { osg::Matrix res_matrix; @@ -264,7 +363,7 @@ sgLoad3DModel_internal(const string &path, offs->getDoubleValue("z-m", 0)); align->setMatrix(res_matrix*tmat); align->addChild(submodel.get()); - submodel_final=align.get(); + submodel_final = align; } submodel_final->setName(sub_props->getStringValue("name", "")); @@ -313,9 +412,15 @@ sgLoad3DModel_internal(const string &path, prop_root, options.get())); } - - std::vector animation_nodes; - animation_nodes = props->getChildren("animation"); + PropertyList effect_nodes = props->getChildren("effect"); + PropertyList animation_nodes = props->getChildren("animation"); + // Some material animations (eventually all) are actually effects. + makeEffectAnimations(animation_nodes, effect_nodes); + { + ref_ptr modelWithEffects + = instantiateEffects(group.get(), effect_nodes, options.get()); + group = static_cast(modelWithEffects.get()); + } for (unsigned i = 0; i < animation_nodes.size(); ++i) /// OSGFIXME: duh, why not only model????? SGAnimation::animate(group.get(), animation_nodes[i], prop_root, diff --git a/simgear/scene/model/animation.cxx b/simgear/scene/model/animation.cxx index 22ba2825..80c08be2 100644 --- a/simgear/scene/model/animation.cxx +++ b/simgear/scene/model/animation.cxx @@ -30,6 +30,10 @@ #include #include #include +#include +#include +#include + #include #include @@ -971,6 +975,15 @@ osg::StateSet* getNormalizeStateSet() class SGDistScaleAnimation::Transform : public osg::Transform { public: + Transform() : _min_v(0.0), _max_v(0.0), _factor(0.0), _offset(0.0) {} + Transform(const Transform& rhs, + const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : osg::Transform(rhs, copyOp), _table(rhs._table), _center(rhs._center), + _min_v(rhs._min_v), _max_v(rhs._max_v), _factor(rhs._factor), + _offset(rhs._offset) + { + } + META_Node(simgear, SGDistScaleAnimation::Transform); Transform(const SGPropertyNode* configNode) { setName(configNode->getStringValue("name", "dist scale animation")); @@ -1018,6 +1031,16 @@ public: return true; } + static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw) + { + const Transform& trans = static_cast(obj); + fw.indent() << "center " << trans._center << "\n"; + fw.indent() << "min_v " << trans._min_v << "\n"; + fw.indent() << "max_v " << trans._max_v << "\n"; + fw.indent() << "factor " << trans._factor << "\n"; + fw.indent() << "offset " << trans._offset << "\n"; + return true; + } private: double computeScaleFactor(osg::NodeVisitor* nv) const { @@ -1061,6 +1084,17 @@ SGDistScaleAnimation::createAnimationGroup(osg::Group& parent) return transform; } +namespace +{ + osgDB::RegisterDotOsgWrapperProxy distScaleAnimationTransformProxy + ( + new SGDistScaleAnimation::Transform, + "SGDistScaleAnimation::Transform", + "Object Node Transform SGDistScaleAnimation::Transform Group", + 0, + &SGDistScaleAnimation::Transform::writeLocalData + ); +} //////////////////////////////////////////////////////////////////////// // Implementation of flash animation @@ -1068,6 +1102,19 @@ SGDistScaleAnimation::createAnimationGroup(osg::Group& parent) class SGFlashAnimation::Transform : public osg::Transform { public: + Transform() : _power(0.0), _factor(0.0), _offset(0.0), _min_v(0.0), + _max_v(0.0), _two_sides(false) + {} + + Transform(const Transform& rhs, + const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : osg::Transform(rhs, copyOp), _center(rhs._center), _axis(rhs._axis), + _power(rhs._power), _factor(rhs._factor), _offset(rhs._offset), + _min_v(rhs._min_v), _max_v(rhs._max_v), _two_sides(rhs._two_sides) + { + } + META_Node(simgear, SGFlashAnimation::Transform); + Transform(const SGPropertyNode* configNode) { setReferenceFrame(RELATIVE_RF); @@ -1124,6 +1171,21 @@ public: return true; } + static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw) + { + const Transform& trans = static_cast(obj); + fw.indent() << "center " << trans._center[0] << " " + << trans._center[1] << " " << trans._center[2] << " " << "\n"; + fw.indent() << "axis " << trans._axis[0] << " " + << trans._axis[1] << " " << trans._axis[2] << " " << "\n"; + fw.indent() << "power " << trans._power << " \n"; + fw.indent() << "min_v " << trans._min_v << "\n"; + fw.indent() << "max_v " << trans._max_v << "\n"; + fw.indent() << "factor " << trans._factor << "\n"; + fw.indent() << "offset " << trans._offset << "\n"; + fw.indent() << "twosides " << (trans._two_sides ? "true" : "false") << "\n"; + return true; + } private: double computeScaleFactor(osg::NodeVisitor* nv) const { @@ -1178,13 +1240,29 @@ SGFlashAnimation::createAnimationGroup(osg::Group& parent) return transform; } +namespace +{ + osgDB::RegisterDotOsgWrapperProxy flashAnimationTransformProxy + ( + new SGFlashAnimation::Transform, + "SGFlashAnimation::Transform", + "Object Node Transform SGFlashAnimation::Transform Group", + 0, + &SGFlashAnimation::Transform::writeLocalData + ); +} //////////////////////////////////////////////////////////////////////// -// Implementation of flash animation +// Implementation of billboard animation //////////////////////////////////////////////////////////////////////// class SGBillboardAnimation::Transform : public osg::Transform { public: + Transform() : _spherical(true) {} + Transform(const Transform& rhs, + const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : osg::Transform(rhs, copyOp), _spherical(rhs._spherical) {} + META_Node(simgear, SGBillboardAnimation::Transform); Transform(const SGPropertyNode* configNode) : _spherical(configNode->getBoolValue("spherical", true)) { @@ -1221,7 +1299,13 @@ public: // Hmm, don't yet know how to get that back ... return false; } + static bool writeLocalData(const osg::Object& obj, osgDB::Output& fw) + { + const Transform& trans = static_cast(obj); + fw.indent() << (trans._spherical ? "true" : "false") << "\n"; + return true; + } private: bool _spherical; }; @@ -1241,6 +1325,17 @@ SGBillboardAnimation::createAnimationGroup(osg::Group& parent) return transform; } +namespace +{ + osgDB::RegisterDotOsgWrapperProxy billboardAnimationTransformProxy + ( + new SGBillboardAnimation::Transform, + "SGBillboardAnimation::Transform", + "Object Node Transform SGBillboardAnimation::Transform Group", + 0, + &SGBillboardAnimation::Transform::writeLocalData + ); +} //////////////////////////////////////////////////////////////////////// // Implementation of a range animation diff --git a/simgear/scene/model/animation.hxx b/simgear/scene/model/animation.hxx index eec16bc4..4ea07fd2 100644 --- a/simgear/scene/model/animation.hxx +++ b/simgear/scene/model/animation.hxx @@ -174,7 +174,6 @@ public: SGDistScaleAnimation(const SGPropertyNode* configNode, SGPropertyNode* modelRoot); virtual osg::Group* createAnimationGroup(osg::Group& parent); -private: class Transform; }; @@ -188,7 +187,7 @@ public: SGFlashAnimation(const SGPropertyNode* configNode, SGPropertyNode* modelRoot); virtual osg::Group* createAnimationGroup(osg::Group& parent); -private: +public: class Transform; }; @@ -202,7 +201,6 @@ public: SGBillboardAnimation(const SGPropertyNode* configNode, SGPropertyNode* modelRoot); virtual osg::Group* createAnimationGroup(osg::Group& parent); -private: class Transform; }; diff --git a/simgear/scene/model/model.cxx b/simgear/scene/model/model.cxx index d41529ad..59f33694 100644 --- a/simgear/scene/model/model.cxx +++ b/simgear/scene/model/model.cxx @@ -7,14 +7,28 @@ #include #endif +#include + +#include + #include +#include +#include +#include #include #include #include +#include +#include #include +#include +#include +#include + #include +#include #include #include #include @@ -60,4 +74,262 @@ SGLoadTexture2D(bool staticTexture, const std::string& path, return texture.release(); } +namespace simgear +{ +using namespace std; +using namespace osg; +using simgear::CopyOp; + +Node* copyModel(Node* model) +{ + const CopyOp::CopyFlags flags = (CopyOp::DEEP_COPY_ALL + & ~CopyOp::DEEP_COPY_TEXTURES + & ~CopyOp::DEEP_COPY_IMAGES + & ~CopyOp::DEEP_COPY_STATESETS + & ~CopyOp::DEEP_COPY_STATEATTRIBUTES + & ~CopyOp::DEEP_COPY_ARRAYS + & ~CopyOp::DEEP_COPY_PRIMITIVES + // This will preserve display lists ... + & ~CopyOp::DEEP_COPY_DRAWABLES + & ~CopyOp::DEEP_COPY_SHAPES); + return (CopyOp(flags))(model); +} + +TextureUpdateVisitor::TextureUpdateVisitor(const osgDB::FilePathList& pathList) : + NodeAndDrawableVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN), + _pathList(pathList) +{ +} + +void TextureUpdateVisitor::apply(Node& node) +{ + StateSet* stateSet = cloneStateSet(node.getStateSet()); + if (stateSet) + node.setStateSet(stateSet); + traverse(node); +} + +void TextureUpdateVisitor::apply(Drawable& drawable) +{ + StateSet* stateSet = cloneStateSet(drawable.getStateSet()); + if (stateSet) + drawable.setStateSet(stateSet); +} + +Texture2D* TextureUpdateVisitor::textureReplace(int unit, const StateAttribute* attr) +{ + using namespace osgDB; + const Texture2D* texture = dynamic_cast(attr); + + if (!texture) + return 0; + + const Image* image = texture->getImage(); + const string* fullFilePath = 0; + if (image) { + // The currently loaded file name + fullFilePath = &image->getFileName(); + + } else { + fullFilePath = &texture->getName(); + } + // The short name + string fileName = getSimpleFileName(*fullFilePath); + if (fileName.empty()) + return 0; + // The name that should be found with the current database path + string fullLiveryFile = findFileInPath(fileName, _pathList); + // If it is empty or they are identical then there is nothing to do + if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath) + return 0; + Image* newImage = readImageFile(fullLiveryFile); + if (!newImage) + return 0; + CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES); + Texture2D* newTexture = static_cast(copyOp(texture)); + if (!newTexture) { + return 0; + } else { + newTexture->setImage(newImage); + return newTexture; + } +} + +StateSet* TextureUpdateVisitor::cloneStateSet(const StateSet* stateSet) +{ + typedef std::pair Tex2D; + vector newTextures; + StateSet* result = 0; + + if (!stateSet) + return 0; + int numUnits = stateSet->getTextureAttributeList().size(); + if (numUnits > 0) { + for (int i = 0; i < numUnits; ++i) { + const StateAttribute* attr + = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE); + Texture2D* newTexture = textureReplace(i, attr); + if (newTexture) + newTextures.push_back(Tex2D(i, newTexture)); + } + if (!newTextures.empty()) { + result = static_cast(stateSet->clone(CopyOp())); + for (vector::iterator i = newTextures.begin(); + i != newTextures.end(); + ++i) { + result->setTextureAttribute(i->first, i->second); + } + } + } + return result; +} + +UserDataCopyVisitor::UserDataCopyVisitor() : + NodeVisitor(NodeVisitor::NODE_VISITOR, + NodeVisitor::TRAVERSE_ALL_CHILDREN) +{ +} + +void UserDataCopyVisitor::apply(Node& node) +{ + ref_ptr userData; + userData = SGSceneUserData::getSceneUserData(&node); + if (userData.valid()) { + SGSceneUserData* newUserData = new SGSceneUserData(*userData); + newUserData->setVelocity(0); + node.setUserData(newUserData); + } + node.traverse(*this); +} + +namespace +{ +class MakeEffectVisitor : public SplicingVisitor +{ +public: + typedef std::map EffectMap; + using SplicingVisitor::apply; + MakeEffectVisitor(const osgDB::ReaderWriter::Options* options = 0) + : _options(options) + { + } + virtual void apply(osg::Group& node); + virtual void apply(osg::Geode& geode); + EffectMap& getEffectMap() { return _effectMap; } + const EffectMap& getEffectMap() const { return _effectMap; } + void setDefaultEffect(SGPropertyNode* effect) + { + _currentEffectParent = effect; + } + SGPropertyNode* getDefaultEffect() { return _currentEffectParent; } +protected: + EffectMap _effectMap; + SGPropertyNode_ptr _currentEffectParent; + osg::ref_ptr _options; +}; + +void MakeEffectVisitor::apply(osg::Group& node) +{ + SGPropertyNode_ptr savedEffectRoot; + const string& nodeName = node.getName(); + bool restoreEffect = false; + if (!nodeName.empty()) { + EffectMap::iterator eitr = _effectMap.find(nodeName); + if (eitr != _effectMap.end()) { + savedEffectRoot = _currentEffectParent; + _currentEffectParent = eitr->second; + restoreEffect = true; + } + } + SplicingVisitor::apply(node); + // If a new node was created, copy the user data too. + ref_ptr userData = SGSceneUserData::getSceneUserData(&node); + if (userData.valid() && _childStack.back().back().get() != &node) + _childStack.back().back()->setUserData(new SGSceneUserData(*userData)); + if (restoreEffect) + _currentEffectParent = savedEffectRoot; +} + +void MakeEffectVisitor::apply(osg::Geode& geode) +{ + if (pushNode(getNewNode(geode))) + return; + osg::StateSet* ss = geode.getStateSet(); + if (!ss) { + pushNode(&geode); + return; + } + SGPropertyNode_ptr ssRoot = new SGPropertyNode; + makeParametersFromStateSet(ssRoot, ss); + SGPropertyNode_ptr effectRoot = new SGPropertyNode; + effect::mergePropertyTrees(effectRoot, ssRoot, _currentEffectParent); + Effect* effect = makeEffect(effectRoot, true, _options); + EffectGeode* eg = dynamic_cast(&geode); + if (eg) { + eg->setEffect(effect); + } else { + eg = new EffectGeode; + eg->setEffect(effect); + ref_ptr userData = SGSceneUserData::getSceneUserData(&geode); + if (userData.valid()) + eg->setUserData(new SGSceneUserData(*userData)); + for (int i = 0; i < geode.getNumDrawables(); ++i) + eg->addDrawable(geode.getDrawable(i)); + } + pushResultNode(&geode, eg); + +} + +} + +namespace +{ +class DefaultEffect : public simgear::Singleton +{ +public: + DefaultEffect() + { + _effect = new SGPropertyNode; + makeChild(_effect.ptr(), "inherits-from") + ->setStringValue("Effects/model-default"); + } + virtual ~DefaultEffect() {} + SGPropertyNode* getEffect() { return _effect.ptr(); } +protected: + SGPropertyNode_ptr _effect; +}; +} + +ref_ptr instantiateEffects(osg::Node* modelGroup, + PropertyList& effectProps, + const osgDB::ReaderWriter::Options* options) +{ + SGPropertyNode_ptr defaultEffectPropRoot; + MakeEffectVisitor visitor(options); + MakeEffectVisitor::EffectMap& emap = visitor.getEffectMap(); + for (PropertyList::iterator itr = effectProps.begin(), + end = effectProps.end(); + itr != end; + ++itr) + { + SGPropertyNode_ptr configNode = *itr; + std::vector objectNames = + configNode->getChildren("object-name"); + SGPropertyNode* defaultNode = configNode->getChild("default"); + if (defaultNode && defaultNode->getValue()) + defaultEffectPropRoot = configNode; + BOOST_FOREACH(SGPropertyNode_ptr objNameNode, objectNames) { + emap.insert(make_pair(objNameNode->getStringValue(), configNode)); + } + configNode->removeChild("default"); + configNode->removeChildren("object-name"); + } + if (!defaultEffectPropRoot) + defaultEffectPropRoot = DefaultEffect::instance()->getEffect(); + visitor.setDefaultEffect(defaultEffectPropRoot.ptr()); + modelGroup->accept(visitor); + osg::NodeList& result = visitor.getResults(); + return ref_ptr(result[0].get()); +} +} // end of model.cxx diff --git a/simgear/scene/model/model.hxx b/simgear/scene/model/model.hxx index bef5a322..49dc0666 100644 --- a/simgear/scene/model/model.hxx +++ b/simgear/scene/model/model.hxx @@ -20,6 +20,8 @@ #include #include +#include +#include osg::Texture2D* SGLoadTexture2D(bool staticTexture, const std::string& path, @@ -54,4 +56,64 @@ SGLoadTexture2D(bool staticTexture, const SGPath& path, mipmaplevels); } +namespace simgear +{ +osg::Node* copyModel(osg::Node* model); + +// Change the StateSets of a model to hold different textures based on +// a livery path. + +class TextureUpdateVisitor : public NodeAndDrawableVisitor +{ +public: + TextureUpdateVisitor(const osgDB::FilePathList& pathList); + virtual void apply(osg::Node& node); + virtual void apply(osg::Drawable& drawable); + // Copied from Mathias' earlier SGTextureUpdateVisitor +protected: + osg::Texture2D* textureReplace(int unit, const osg::StateAttribute* attr); + osg::StateSet* cloneStateSet(const osg::StateSet* stateSet); +private: + osgDB::FilePathList _pathList; +}; + +// Create new userdata structs in a copied model. +// The BVH trees are shared with the original model, but the velocity fields +// should usually be distinct fields for distinct models. +class UserDataCopyVisitor : public osg::NodeVisitor +{ +public: + UserDataCopyVisitor(); + virtual void apply(osg::Node& node); +}; + +/** + * Transform an OSG subgraph by substituting Effects and EffectGeodes + * for osg::Geodes with osg::StateSets. This is only guaranteed to + * work for models prouced by the .ac loader. + * + * returns a copy if any nodes are changed + */ +osg::ref_ptr +instantiateEffects(osg::Node* model, + PropertyList& effectProps, + const osgDB::ReaderWriter::Options* options); + +/** + * Transform an OSG subgraph by substituting the Effects and + * EffectGeodes for osg::Geodes with osg::StateSets, inheriting from + * the default model effect. This is only guaranteed to work for + * models prouced by the .ac loader. + * + * returns a copy if any nodes are changed + */ + +inline osg::ref_ptr +instantiateEffects(osg::Node* model, + const osgDB::ReaderWriter::Options* options) +{ + PropertyList effectProps; + return instantiateEffects(model, effectProps, options); +} +} #endif // __MODEL_HXX diff --git a/simgear/scene/model/modellib.cxx b/simgear/scene/model/modellib.cxx index a3a779fd..6a13c516 100644 --- a/simgear/scene/model/modellib.cxx +++ b/simgear/scene/model/modellib.cxx @@ -19,6 +19,8 @@ # include #endif +#include + #include #include #include @@ -26,6 +28,7 @@ #include #include #include +#include #include #include "SGPagedLOD.hxx" @@ -59,6 +62,21 @@ SGModelLib::~SGModelLib() { } +namespace +{ +osg::Node* loadFile(const string& path, osgDB::ReaderWriter::Options* options) +{ + using namespace osg; + using namespace osgDB; + ref_ptr model = readRefNodeFile(path, options); + if (!model) + return 0; + if (boost::iends_with(path, ".ac")) + model = instantiateEffects(model.get(), options); + return model.release(); +} +} + osg::Node* SGModelLib::loadModel(const string &path, SGPropertyNode *prop_root, @@ -67,7 +85,7 @@ SGModelLib::loadModel(const string &path, osg::ref_ptr opt = new SGReaderWriterXMLOptions(*(osgDB::Registry::instance()->getOptions())); opt->setPropRoot(prop_root); opt->setModelData(data); - osg::Node *n = readNodeFile(path, opt.get()); + osg::Node *n = loadFile(path, opt.get()); if (n && n->getName().empty()) n->setName("Direct loaded model \"" + path + "\""); return n; @@ -82,7 +100,7 @@ SGModelLib::loadModel(const string &path, osg::ref_ptr opt = new SGReaderWriterXMLOptions(*(osgDB::Registry::instance()->getOptions())); opt->setPropRoot(prop_root); opt->setLoadPanel(pf); - return readNodeFile(path, opt.get()); + return loadFile(path, opt.get()); } osg::Node* diff --git a/simgear/scene/sky/CloudShaderGeometry.cxx b/simgear/scene/sky/CloudShaderGeometry.cxx index 10d50acb..229710db 100755 --- a/simgear/scene/sky/CloudShaderGeometry.cxx +++ b/simgear/scene/sky/CloudShaderGeometry.cxx @@ -121,7 +121,7 @@ void CloudShaderGeometry::drawImplementation(RenderInfo& renderInfo) const } } -void CloudShaderGeometry::addSprite(SGVec3f& p, int tx, int ty, +void CloudShaderGeometry::addSprite(const SGVec3f& p, int tx, int ty, float w, float h, float s, float cull, float cloud_height) { diff --git a/simgear/scene/sky/CloudShaderGeometry.hxx b/simgear/scene/sky/CloudShaderGeometry.hxx index 8a9c31c5..cd00da8d 100755 --- a/simgear/scene/sky/CloudShaderGeometry.hxx +++ b/simgear/scene/sky/CloudShaderGeometry.hxx @@ -69,7 +69,8 @@ class CloudShaderGeometry : public osg::Drawable META_Object(flightgear, CloudShaderGeometry); struct CloudSprite { - CloudSprite(SGVec3f& p, int tx, int ty, float w, float h, float s, float ch) : + CloudSprite(const SGVec3f& p, int tx, int ty, float w, float h, + float s, float ch) : position(p), texture_index_x(tx), texture_index_y(ty), width(w), height(h), shade(s), cloud_height(ch) { } @@ -106,7 +107,8 @@ class CloudShaderGeometry : public osg::Drawable _geometry = geometry; } - void addSprite(SGVec3f& p, int tx, int ty, float w, float h, float s, float cull, float cloud_height); + void addSprite(const SGVec3f& p, int tx, int ty, float w, float h, + float s, float cull, float cloud_height); osg::ref_ptr _geometry; diff --git a/simgear/scene/sky/cloudfield.cxx b/simgear/scene/sky/cloudfield.cxx index 770f4da0..013efe16 100644 --- a/simgear/scene/sky/cloudfield.cxx +++ b/simgear/scene/sky/cloudfield.cxx @@ -71,7 +71,6 @@ float SGCloudField::fieldSize = 50000.0f; double SGCloudField::timer_dt = 0.0; float SGCloudField::view_distance = 20000.0f; sgVec3 SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y; -SGCloudField::StateSetMap SGCloudField::cloudTextureMap; // reposition the cloud layer at the specified origin and orientation bool SGCloudField::reposition( const SGVec3f& p, const SGVec3f& up, double lon, double lat, diff --git a/simgear/scene/sky/cloudfield.hxx b/simgear/scene/sky/cloudfield.hxx index beab904d..1b2f97f1 100644 --- a/simgear/scene/sky/cloudfield.hxx +++ b/simgear/scene/sky/cloudfield.hxx @@ -137,9 +137,6 @@ public: void applyCoverage(void); void applyVisRange(void); - typedef std::map > StateSetMap; - static StateSetMap cloudTextureMap; - static osg::Fog* getFog() { return CloudFog::instance()->fog.get(); diff --git a/simgear/scene/sky/newcloud.cxx b/simgear/scene/sky/newcloud.cxx index d641b2c9..f4eb8de2 100644 --- a/simgear/scene/sky/newcloud.cxx +++ b/simgear/scene/sky/newcloud.cxx @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -60,75 +61,14 @@ using namespace simgear; using namespace osg; -typedef std::map > StateSetMap; +namespace +{ +typedef std::map > EffectMap; +EffectMap effectMap; +} -StateSetMap cloudTextureMap; double SGNewCloud::sprite_density = 1.0; -static char vertexShaderSource[] = - "#version 120\n" - "\n" - "varying float fogFactor;\n" - "attribute vec3 usrAttr1;\n" - "attribute vec3 usrAttr2;\n" - "float textureIndexX = usrAttr1.r;\n" - "float textureIndexY = usrAttr1.g;\n" - "float wScale = usrAttr1.b;\n" - "float hScale = usrAttr2.r;\n" - "float shade = usrAttr2.g;\n" - "float cloud_height = usrAttr2.b;\n" - "void main(void)\n" - "{\n" - " gl_TexCoord[0] = gl_MultiTexCoord0 + vec4(textureIndexX, textureIndexY, 0.0, 0.0);\n" - " vec4 ep = gl_ModelViewMatrixInverse * vec4(0.0,0.0,0.0,1.0);\n" - " vec4 l = gl_ModelViewMatrixInverse * vec4(0.0,0.0,1.0,1.0);\n" - " vec3 u = normalize(ep.xyz - l.xyz);\n" -// Find a rotation matrix that rotates 1,0,0 into u. u, r and w are -// the columns of that matrix. - " vec3 absu = abs(u);\n" - " vec3 r = normalize(vec3(-u.y, u.x, 0));\n" - " vec3 w = cross(u, r);\n" -// Do the matrix multiplication by [ u r w pos]. Assume no -// scaling in the homogeneous component of pos. - " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" - " gl_Position.xyz = gl_Vertex.x * u;\n" - " gl_Position.xyz += gl_Vertex.y * r * wScale;\n" - " gl_Position.xyz += gl_Vertex.z * w * hScale;\n" - " gl_Position.xyz += gl_Color.xyz;\n" -// Determine a lighting normal based on the vertex position from the -// center of the cloud, so that sprite on the opposite side of the cloud to the sun are darker. - " float n = dot(normalize(- gl_LightSource[0].position.xyz), normalize(mat3x3(gl_ModelViewMatrix) * (- gl_Position.xyz)));\n" -// Determine the position - used for fog and shading calculations - " vec3 ecPosition = vec3(gl_ModelViewMatrix * gl_Position);\n" - " float fogCoord = abs(ecPosition.z);\n" - " float fract = smoothstep(0.0, cloud_height, gl_Position.z + cloud_height);\n" -// Final position of the sprite - " gl_Position = gl_ModelViewProjectionMatrix * gl_Position;\n" -// Determine the shading of the sprite based on its vertical position and position relative to the sun. - " n = min(smoothstep(-0.5, 0.0, n), fract);\n" -// Determine the shading based on a mixture from the backlight to the front - " vec4 backlight = gl_LightSource[0].diffuse * shade;\n" - " gl_FrontColor = mix(backlight, gl_LightSource[0].diffuse, n);\n" - " gl_FrontColor += gl_FrontLightModelProduct.sceneColor;\n" -// As we get within 100m of the sprite, it is faded out. Equally at large distances it also fades out. - " gl_FrontColor.a = min(smoothstep(10.0, 100.0, fogCoord), 1 - smoothstep(15000.0, 20000.0, fogCoord));\n" - " gl_BackColor = gl_FrontColor;\n" -// Fog doesn't affect clouds as much as other objects. - " fogFactor = exp( -gl_Fog.density * fogCoord * 0.5);\n" - " fogFactor = clamp(fogFactor, 0.0, 1.0);\n" - "}\n"; - -static char fragmentShaderSource[] = - "uniform sampler2D baseTexture; \n" - "varying float fogFactor;\n" - "\n" - "void main(void)\n" - "{\n" - " vec4 base = texture2D( baseTexture, gl_TexCoord[0].st);\n" - " vec4 finalColor = base * gl_Color;\n" - " gl_FragColor.rgb = mix(gl_Fog.color.rgb, finalColor.rgb, fogFactor );\n" - " gl_FragColor.a = mix(0.0, finalColor.a, fogFactor);\n" - "}\n"; SGNewCloud::SGNewCloud(string type, const SGPath &tex_path, @@ -160,75 +100,24 @@ SGNewCloud::SGNewCloud(string type, texture(tex), name(type) { - // Create a new StateSet for the texture, if required. - StateSetMap::iterator iter = SGCloudField::cloudTextureMap.find(texture); - - if (iter == SGCloudField::cloudTextureMap.end()) { - stateSet = new osg::StateSet; - - osg::ref_ptr options = makeOptionsFromPath(tex_path); - - osg::Texture2D *tex = new osg::Texture2D; - tex->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP ); - tex->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP ); - tex->setImage(osgDB::readImageFile(texture, options.get())); - - StateAttributeFactory* attribFactory = StateAttributeFactory::instance(); - - stateSet->setMode(GL_LIGHTING, osg::StateAttribute::ON); - stateSet->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - - // Fog handling - stateSet->setAttributeAndModes(attribFactory->getSmoothShadeModel()); - stateSet->setAttributeAndModes(attribFactory->getStandardBlendFunc()); - - stateSet->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON ); - stateSet->setRenderBinDetails(osg::StateSet::TRANSPARENT_BIN, "DepthSortedBin"); - // Turn off z buffer writes. Standard hack for - // semi-transparent geometry to avoid sorting / flickering - // artifacts. - stateSet->setAttributeAndModes(attribFactory->getDepthWritesDisabled()); - static ref_ptr alphaFunc; - static ref_ptr program; - static ref_ptr baseTextureSampler; - static ref_ptr material; - - // Generate the shader etc, if we don't already have one. - if (!program.valid()) { - alphaFunc = new AlphaFunc; - alphaFunc->setFunction(AlphaFunc::GREATER,0.01f); - program = new Program; - baseTextureSampler = new osg::Uniform("baseTexture", 0); - Shader* vertex_shader = new Shader(Shader::VERTEX, vertexShaderSource); - program->addShader(vertex_shader); - program->addBindAttribLocation("usrAttr1", CloudShaderGeometry::USR_ATTR_1); - program->addBindAttribLocation("usrAttr2", CloudShaderGeometry::USR_ATTR_2); - Shader* fragment_shader = new Shader(Shader::FRAGMENT, fragmentShaderSource); - program->addShader(fragment_shader); - material = new Material; - // Don´t track vertex color - material->setColorMode(Material::OFF); - - // We don't actually use the material information either - see shader. - material->setAmbient(Material::FRONT_AND_BACK, - Vec4(0.5f, 0.5f, 0.5f, 1.0f)); - material->setDiffuse(Material::FRONT_AND_BACK, - Vec4(0.5f, 0.5f, 0.5f, 1.0f)); - } - - stateSet->setAttributeAndModes(alphaFunc.get()); - stateSet->setAttribute(program.get()); - stateSet->addUniform(baseTextureSampler.get()); - stateSet->setMode(GL_VERTEX_PROGRAM_TWO_SIDE, StateAttribute::ON); - stateSet->setAttribute(material.get()); - - // Add the newly created texture to the map for use later. - SGCloudField::cloudTextureMap.insert(StateSetMap::value_type(texture, stateSet)); + // Create a new Effect for the texture, if required. + EffectMap::iterator iter = effectMap.find(texture); + if (iter == effectMap.end()) { + SGPropertyNode_ptr pcloudEffect = new SGPropertyNode; + makeChild(pcloudEffect, "inherits-from")->setValue("Effects/cloud"); + setValue(makeChild(makeChild(makeChild(pcloudEffect, "parameters"), + "texture"), + "image"), + texture); + osg::ref_ptr options + = makeOptionsFromPath(tex_path); + if ((effect = makeEffect(pcloudEffect, true, options))) + effectMap.insert(EffectMap::value_type(texture, effect)); } else { - stateSet = iter->second.get(); + effect = iter->second.get(); } - - quad = createOrthQuad(min_sprite_width, min_sprite_height, num_textures_x, num_textures_y); + quad = createOrthQuad(min_sprite_width, min_sprite_height, + num_textures_x, num_textures_y); } SGNewCloud::~SGNewCloud() { @@ -285,9 +174,9 @@ static float Rnd(float n) { } #endif -osg::ref_ptr SGNewCloud::genCloud() { +osg::ref_ptr SGNewCloud::genCloud() { - osg::ref_ptr geode = new Geode; + osg::ref_ptr geode = new EffectGeode; CloudShaderGeometry* sg = new CloudShaderGeometry(num_textures_x, num_textures_y, max_width, max_height); @@ -325,9 +214,7 @@ osg::ref_ptr SGNewCloud::genCloud() { z = height * cos(elev) * 0.5f; } - SGVec3f *pos = new SGVec3f(x, y, z); - - // Determine the height and width as scaling factors on the minimum size (used to create the quad). + // Determine the height and width as scaling factors on the minimum size (used to create the quad) float sprite_width = 1.0f + sg_random() * (max_sprite_width - min_sprite_width) / min_sprite_width; float sprite_height = 1.0f + sg_random() * (max_sprite_height - min_sprite_height) / min_sprite_height; @@ -353,7 +240,7 @@ osg::ref_ptr SGNewCloud::genCloud() { int index_y = (int) floor((z / height + 0.5f) * num_textures_y); if (index_y == num_textures_y) { index_y--; } - sg->addSprite(*pos, + sg->addSprite(SGVec3f(x, y, z), index_x, index_y, sprite_width, @@ -366,7 +253,7 @@ osg::ref_ptr SGNewCloud::genCloud() { sg->setGeometry(quad); geode->addDrawable(sg); geode->setName("3D cloud"); - geode->setStateSet(stateSet.get()); + geode->setEffect(effect.get()); return geode; } diff --git a/simgear/scene/sky/newcloud.hxx b/simgear/scene/sky/newcloud.hxx index be5549ff..28f8a9c3 100644 --- a/simgear/scene/sky/newcloud.hxx +++ b/simgear/scene/sky/newcloud.hxx @@ -31,6 +31,9 @@ #include "bbcache.hxx" +#include +#include + using std::string; using std::vector; @@ -59,7 +62,7 @@ public: ~SGNewCloud(); // Generate a Cloud - osg::ref_ptr genCloud (); + osg::ref_ptr genCloud (); static double getDensity(void) { @@ -90,7 +93,7 @@ private: const string texture; const string name; osg::Geometry* quad; - osg::ref_ptr stateSet; + osg::ref_ptr effect; static double sprite_density; osg::Geometry* createOrthQuad(float w, float h, int varieties_x, int varieties_y); diff --git a/simgear/scene/tgdb/SGReaderWriterBTG.cxx b/simgear/scene/tgdb/SGReaderWriterBTG.cxx index 16602a5a..ad3a6774 100644 --- a/simgear/scene/tgdb/SGReaderWriterBTG.cxx +++ b/simgear/scene/tgdb/SGReaderWriterBTG.cxx @@ -76,7 +76,7 @@ SGReaderWriterBTG::readNode(const std::string& fileName, typedef ModelRegistryCallback BTGCallback; diff --git a/simgear/scene/tgdb/TreeBin.cxx b/simgear/scene/tgdb/TreeBin.cxx index 8c639139..5f0c1638 100644 --- a/simgear/scene/tgdb/TreeBin.cxx +++ b/simgear/scene/tgdb/TreeBin.cxx @@ -26,18 +26,11 @@ #include -#include -#include -#include #include #include -#include #include #include #include -#include -#include -#include #include #include @@ -45,6 +38,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -167,14 +163,17 @@ Geometry* createTreeGeometry(float width, float height, int varieties) (*rotation)[1] = PI_2; quadGeom->setFogCoordArray(rotation); quadGeom->setFogCoordBinding(Geometry::BIND_PER_PRIMITIVE_SET); + // The primitive sets render the same geometry, but the second + // will rotated 90 degrees by the vertex shader, which uses the + // fog coordinate as a rotation. for (int i = 0; i < 2; ++i) quadGeom->addPrimitiveSet(new DrawArrays(PrimitiveSet::QUADS)); return quadGeom; } -Geode* createTreeGeode(float width, float height, int varieties) +EffectGeode* createTreeGeode(float width, float height, int varieties) { - Geode* result = new Geode; + EffectGeode* result = new EffectGeode; result->addDrawable(createTreeGeometry(width, height, varieties)); return result; } @@ -204,67 +203,31 @@ void addTreeToLeafGeode(Geode* geode, const SGVec3f& p) } } - static char vertexShaderSource[] = - "varying float fogFactor;\n" - "\n" - "void main(void)\n" - "{\n" - " float numVarieties = gl_Normal.z;\n" - " float texFract = floor(fract(gl_MultiTexCoord0.x) * numVarieties) / numVarieties;\n" - " texFract += floor(gl_MultiTexCoord0.x) / numVarieties;\n" - " float sr = sin(gl_FogCoord);\n" - " float cr = cos(gl_FogCoord);\n" - " gl_TexCoord[0] = vec4(texFract, gl_MultiTexCoord0.y, 0.0, 0.0);\n" - // scaling - " vec3 position = gl_Vertex.xyz * gl_Normal.xxy;\n" - // Rotation of the generic quad to specific one for the tree. - " position.xy = vec2(dot(position.xy, vec2(cr, sr)), dot(position.xy, vec2(-sr, cr)));\n" - " position = position + gl_Color.xyz;\n" - " gl_Position = gl_ModelViewProjectionMatrix * vec4(position,1.0);\n" - " vec3 ecPosition = vec3(gl_ModelViewMatrix * vec4(position, 1.0));\n" - " float n = dot(normalize(gl_LightSource[0].position.xyz), normalize(-ecPosition));\n" - " vec3 diffuse = gl_FrontMaterial.diffuse.rgb * max(0.1, n);\n" - " vec4 ambientColor = gl_FrontLightModelProduct.sceneColor + gl_LightSource[0].ambient * gl_FrontMaterial.ambient;\n" - " gl_FrontColor = ambientColor + gl_LightSource[0].diffuse * vec4(diffuse, 1.0);\n" - " gl_BackColor = gl_FrontColor;\n" - " float fogCoord = abs(ecPosition.z);\n" - " fogFactor = exp( -gl_Fog.density * gl_Fog.density * fogCoord * fogCoord);\n" - " fogFactor = clamp(fogFactor, 0.0, 1.0);\n" - "}\n"; +typedef std::map > EffectMap; -static char fragmentShaderSource[] = - "uniform sampler2D baseTexture; \n" - "varying float fogFactor;\n" - "\n" - "void main(void) \n" - "{ \n" - " vec4 base = texture2D( baseTexture, gl_TexCoord[0].st);\n" - " vec4 finalColor = base * gl_Color;\n" - " gl_FragColor = mix(gl_Fog.color, finalColor, fogFactor );\n" - "}\n"; - -typedef std::map > StateSetMap; - -static StateSetMap treeTextureMap; +static EffectMap treeEffectMap; // Helper classes for creating the quad tree namespace { struct MakeTreesLeaf { - MakeTreesLeaf(float range, int varieties, float width, float height) : + MakeTreesLeaf(float range, int varieties, float width, float height, + Effect* effect) : _range(range), _varieties(varieties), - _width(width), _height(height) {} + _width(width), _height(height), _effect(effect) {} MakeTreesLeaf(const MakeTreesLeaf& rhs) : _range(rhs._range), - _varieties(rhs._varieties), _width(rhs._width), _height(rhs._height) + _varieties(rhs._varieties), _width(rhs._width), _height(rhs._height), + _effect(rhs._effect) {} LOD* operator() () const { LOD* result = new LOD; - Geode* geode = createTreeGeode(_width, _height, _varieties); + EffectGeode* geode = createTreeGeode(_width, _height, _varieties); + geode->setEffect(_effect.get()); result->addChild(geode, 0, _range); return result; } @@ -272,6 +235,7 @@ struct MakeTreesLeaf int _varieties; float _width; float _height; + ref_ptr _effect; }; struct AddTreesLeafObject @@ -317,51 +281,19 @@ osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform) // Set up some shared structures. ref_ptr group; - osg::StateSet* stateset = 0; - StateSetMap::iterator iter = treeTextureMap.find(forest.texture); - if (iter == treeTextureMap.end()) { - osg::Texture2D *tex = new osg::Texture2D; - tex->setWrap( osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP ); - tex->setWrap( osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP ); - tex->setImage(osgDB::readImageFile(forest.texture)); - - static ref_ptr alphaFunc; - static ref_ptr program; - static ref_ptr baseTextureSampler; - static ref_ptr material; - - stateset = new osg::StateSet; - stateset->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON ); - stateset->setRenderBinDetails(RANDOM_OBJECTS_BIN, "DepthSortedBin"); - if (!program.valid()) { - alphaFunc = new AlphaFunc; - alphaFunc->setFunction(AlphaFunc::GEQUAL,0.33f); - program = new Program; - baseTextureSampler = new osg::Uniform("baseTexture", 0); - Shader* vertex_shader = new Shader(Shader::VERTEX, - vertexShaderSource); - program->addShader(vertex_shader); - Shader* fragment_shader = new Shader(Shader::FRAGMENT, - fragmentShaderSource); - program->addShader(fragment_shader); - material = new Material; - // Don´t track vertex color - material->setColorMode(Material::OFF); - material->setAmbient(Material::FRONT_AND_BACK, - Vec4(1.0f, 1.0f, 1.0f, 1.0f)); - material->setDiffuse(Material::FRONT_AND_BACK, - Vec4(1.0f, 1.0f, 1.0f, 1.0f)); - } - stateset->setAttributeAndModes(alphaFunc.get()); - stateset->setAttribute(program.get()); - stateset->addUniform(baseTextureSampler.get()); - stateset->setMode(GL_VERTEX_PROGRAM_TWO_SIDE, StateAttribute::ON); - stateset->setAttribute(material.get()); - - treeTextureMap.insert(StateSetMap::value_type(forest.texture, - stateset)); + Effect* effect = 0; + EffectMap::iterator iter = treeEffectMap.find(forest.texture); + if (iter == treeEffectMap.end()) { + SGPropertyNode_ptr effectProp = new SGPropertyNode; + makeChild(effectProp, "inherits-from")->setStringValue("Effects/tree"); + SGPropertyNode* params = makeChild(effectProp, "parameters"); + // emphasize n = 0 + params->getChild("texture", 0, true)->getChild("image", 0, true) + ->setStringValue(forest.texture); + effect = makeEffect(effectProp, true); + treeEffectMap.insert(EffectMap::value_type(forest.texture, effect)); } else { - stateset = iter->second.get(); + effect = iter->second.get(); } // Now, create a quadtree for the forest. { @@ -369,7 +301,7 @@ osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform) quadtree(GetTreeCoord(), AddTreesLeafObject(), SG_TREE_QUAD_TREE_DEPTH, MakeTreesLeaf(forest.range, forest.texture_varieties, - forest.width, forest.height)); + forest.width, forest.height, effect)); // Transform tree positions from the "geocentric" positions we // get from the scenery polys into the local Z-up coordinate // system. @@ -384,7 +316,6 @@ osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform) MatrixTransform* mt = new MatrixTransform(transform); for (size_t i = 0; i < group->getNumChildren(); ++i) mt->addChild(group->getChild(i)); - mt->setStateSet(stateset); return mt; } diff --git a/simgear/scene/tgdb/userdata.cxx b/simgear/scene/tgdb/userdata.cxx index fab67b2f..11d69e12 100644 --- a/simgear/scene/tgdb/userdata.cxx +++ b/simgear/scene/tgdb/userdata.cxx @@ -64,3 +64,10 @@ osg::Node* sgGetRandomModel(SGMatModel *obj) { return obj->get_random_model( root_props ); } +namespace simgear +{ +SGPropertyNode* getPropertyRoot() +{ + return root_props; +} +} diff --git a/simgear/scene/tgdb/userdata.hxx b/simgear/scene/tgdb/userdata.hxx index e5a870e3..ab0a6a55 100644 --- a/simgear/scene/tgdb/userdata.hxx +++ b/simgear/scene/tgdb/userdata.hxx @@ -44,4 +44,12 @@ void sgUserDataInit(SGPropertyNode *p); */ osg::Node* sgGetRandomModel(SGMatModel *obj); +namespace simgear +{ +/** + * Get the property root for the simulation + */ +SGPropertyNode* getPropertyRoot(); +} + #endif // _SG_USERDATA_HXX diff --git a/simgear/scene/util/CopyOp.cxx b/simgear/scene/util/CopyOp.cxx new file mode 100644 index 00000000..48a5bd0d --- /dev/null +++ b/simgear/scene/util/CopyOp.cxx @@ -0,0 +1,40 @@ +// CopyOp.cxx - Simgear CopyOp for copying our own classes +// +// Copyright (C) 2009 Tim Moore timoore@redhat.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + +#include "CopyOp.hxx" + +#include +#include + +namespace simgear +{ +osg::Object* CopyOp::operator()(const osg::Object* obj) const +{ + if (dynamic_cast(obj) + || dynamic_cast(obj)) { + if (_flags & DEEP_COPY_STATESETS) + return obj->clone(*this); + else + return const_cast(obj); + } + else { + return osg::CopyOp::operator()(obj); + } +} +} diff --git a/simgear/scene/util/CopyOp.hxx b/simgear/scene/util/CopyOp.hxx new file mode 100644 index 00000000..891b5a08 --- /dev/null +++ b/simgear/scene/util/CopyOp.hxx @@ -0,0 +1,37 @@ +// CopyOp.hxx - Simgear CopyOp for copying our own classes +// +// Copyright (C) 2009 Tim Moore timoore@redhat.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + +#ifndef SIMGEAR_COPYOP_HXX +#define SIMGEAR_COPYOP_HXX 1 +#include + +namespace simgear +{ +class CopyOp : public osg::CopyOp +{ +public: + CopyOp(osg::CopyOp::CopyFlags flags = osg::CopyOp::SHALLOW_COPY) + : osg::CopyOp(flags) + { + } + using osg::CopyOp::operator(); + virtual osg::Object* operator()(const osg::Object* obj) const; +}; +} +#endif diff --git a/simgear/scene/util/Makefile.am b/simgear/scene/util/Makefile.am index 567ec697..376f72f9 100644 --- a/simgear/scene/util/Makefile.am +++ b/simgear/scene/util/Makefile.am @@ -14,11 +14,14 @@ include_HEADERS = \ SGStateAttributeVisitor.hxx \ SGTextureStateAttributeVisitor.hxx \ SGUpdateVisitor.hxx \ + CopyOp.hxx \ NodeAndDrawableVisitor.hxx \ PrimitiveUtils.hxx \ QuadTreeBuilder.hxx \ RenderConstants.hxx \ + SplicingVisitor.hxx \ StateAttributeFactory.hxx \ + UpdateOnceCallback.hxx \ VectorArrayAdapter.hxx @@ -28,9 +31,12 @@ libsgutil_a_SOURCES = \ SGSceneUserData.cxx \ SGStateAttributeVisitor.cxx \ SGTextureStateAttributeVisitor.cxx \ + CopyOp.cxx \ NodeAndDrawableVisitor.cxx \ PrimitiveUtils.cxx \ + SplicingVisitor.cxx \ StateAttributeFactory.cxx \ - QuadTreeBuilder.cxx + QuadTreeBuilder.cxx \ + UpdateOnceCallback.cxx INCLUDES = -I$(top_srcdir) diff --git a/simgear/scene/util/SGSceneUserData.cxx b/simgear/scene/util/SGSceneUserData.cxx index c08fce0e..2388f09d 100644 --- a/simgear/scene/util/SGSceneUserData.cxx +++ b/simgear/scene/util/SGSceneUserData.cxx @@ -23,6 +23,10 @@ # include #endif +#include +#include +#include + #include "SGSceneUserData.hxx" SGSceneUserData* @@ -86,3 +90,38 @@ SGSceneUserData::addPickCallback(SGPickCallback* pickCallback) return; _pickCallbacks.push_back(pickCallback); } + +bool SGSceneUserData_writeLocalData(const osg::Object& obj, osgDB::Output& fw) +{ + const SGSceneUserData& data = static_cast(obj); + + unsigned numPickCallbacks = data.getNumPickCallbacks(); + if (numPickCallbacks > 0) + fw.indent() << "num_pickCallbacks " << numPickCallbacks << "\n"; + if (data.getBVHNode()) + fw.indent() << "hasBVH true\n"; + const SGSceneUserData::Velocity* vel = data.getVelocity(); + if (vel) { + fw.indent() << "velocity {\n"; + fw.moveIn(); + fw.indent() << "linear " << vel->linear << "\n"; + fw.indent() << "angular " << vel->angular << "\n"; + fw.indent() << "referenceTime " << vel->referenceTime << "\n"; + fw.indent() << "id " << static_cast(vel->id) << "\n"; + fw.moveOut(); + fw.indent() << "}\n"; + } + return true; +} + +namespace +{ +osgDB::RegisterDotOsgWrapperProxy SGSceneUserDataProxy +( + new SGSceneUserData, + "simgear::SGSceneUserData", + "Object simgear::SGSceneUserData", + 0, + &SGSceneUserData_writeLocalData + ); +} diff --git a/simgear/scene/util/SGSceneUserData.hxx b/simgear/scene/util/SGSceneUserData.hxx index 4f3f38fc..1296e9c3 100644 --- a/simgear/scene/util/SGSceneUserData.hxx +++ b/simgear/scene/util/SGSceneUserData.hxx @@ -23,13 +23,21 @@ #define SG_SCENE_USERDATA_HXX #include -#include #include +#include #include #include "SGPickCallback.hxx" -class SGSceneUserData : public osg::Referenced { +class SGSceneUserData : public osg::Object { public: + META_Object(simgear, SGSceneUserData); + SGSceneUserData() {} + SGSceneUserData(const SGSceneUserData& rhs, + const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) + : _bvhNode(rhs._bvhNode), _velocity(rhs._velocity), + _pickCallbacks(rhs._pickCallbacks) + { + } static SGSceneUserData* getSceneUserData(osg::Node* node); static const SGSceneUserData* getSceneUserData(const osg::Node* node); static SGSceneUserData* getOrCreateSceneUserData(osg::Node* node); diff --git a/simgear/scene/util/SplicingVisitor.cxx b/simgear/scene/util/SplicingVisitor.cxx new file mode 100644 index 00000000..66558377 --- /dev/null +++ b/simgear/scene/util/SplicingVisitor.cxx @@ -0,0 +1,113 @@ +#include "SplicingVisitor.hxx" + +namespace simgear +{ +using namespace osg; + +SplicingVisitor::SplicingVisitor() + : NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN) +{ + _childStack.push_back(NodeList()); +} + +void SplicingVisitor::reset() +{ + _childStack.clear(); + NodeVisitor::reset(); +} + +NodeList SplicingVisitor::traverse(Node& node) +{ + NodeList result; + _childStack.push_back(NodeList()); + NodeVisitor::traverse(node); + result = _childStack.back(); + _childStack.pop_back(); + return result; +} +void SplicingVisitor::apply(Node& node) +{ + NodeVisitor::traverse(node); + pushNode(&node); +} + +void SplicingVisitor::apply(Group& node) +{ + if (pushNode(getNewNode(node))) + return; + pushResultNode(&node, &node, traverse(node)); +} + +Group* SplicingVisitor::pushResultNode(Group* node, Group* newNode, + const NodeList& children) +{ + ref_ptr result; + if (node == newNode) { + result = copyIfNeeded(*node, children); + } else { + result = newNode; + for (NodeList::const_iterator itr = children.begin(), end = children.end(); + itr != end; + ++itr) + result->addChild(itr->get()); + } + _childStack.back().push_back(result); + recordNewNode(node, result); + return result; +} + +Node* SplicingVisitor::pushResultNode(Node* node, Node* newNode) +{ + _childStack.back().push_back(newNode); + recordNewNode(node, newNode); + return newNode; +} + +Node* SplicingVisitor::pushNode(Node* node) +{ + if (node) + _childStack.back().push_back(node); + return node; +} + +Node* SplicingVisitor::getResult() +{ + NodeList& top = _childStack.at(0); + if (top.empty()) { + return 0; + } else if (top.size() == 1) { + return top[0].get(); + } else { + Group* result = new Group; + for (NodeList::iterator itr = top.begin(), end = top.end(); + itr != end; + ++itr) + result->addChild(itr->get()); + return result; + } +} + +Node* SplicingVisitor::getNewNode(osg::Node* node) +{ + ref_ptr tmpPtr(node); + NodeMap::iterator itr; + try { + itr = _visited.find(tmpPtr); + } + catch (...) { + tmpPtr.release(); + throw; + } + if (itr == _visited.end()) + return 0; + else + return itr->second.get(); +} + +bool SplicingVisitor::recordNewNode(osg::Node* oldNode, osg::Node* newNode) +{ + ref_ptr oldTmp(oldNode); + ref_ptr newTmp(newNode); + return _visited.insert(std::make_pair(oldTmp, newTmp)).second; +} +} diff --git a/simgear/scene/util/SplicingVisitor.hxx b/simgear/scene/util/SplicingVisitor.hxx new file mode 100644 index 00000000..8757e941 --- /dev/null +++ b/simgear/scene/util/SplicingVisitor.hxx @@ -0,0 +1,93 @@ +#ifndef SIMGEAR_SPLICINGVISITOR_HXX +#define SIMGEAR_SPLICINGVISITOR_HXX 1 + +#include +#include +#include +#include +#include + +namespace simgear +{ +class SplicingVisitor : public osg::NodeVisitor +{ +public: + META_NodeVisitor(simgear,SplicingVisitor); + SplicingVisitor(); + virtual ~SplicingVisitor() {} + virtual void reset(); + osg::NodeList traverse(osg::Node& node); + using osg::NodeVisitor::apply; + virtual void apply(osg::Node& node); + virtual void apply(osg::Group& node); + template + static T* copyIfNeeded(T& node, const osg::NodeList& children); + template + static T* copy(T& node, const osg::NodeList& children); + /** + * Push the result of processing this node. + * + * If the child list is not equal to the node's current children, + * make a copy of the node. Record the (possibly new) node which + * should be the returned result if the node is visited again. + */ + osg::Group* pushResultNode(osg::Group* node, osg::Group* newNode, + const osg::NodeList& children); + /** + * Push the result of processing this node. + * + * Record the (possibly new) node which should be the returned + * result if the node is visited again. + */ + osg::Node* pushResultNode(osg::Node* node, osg::Node* newNode); + /** + * Push some node onto the list of result nodes. + */ + osg::Node* pushNode(osg::Node* node); + osg::Node* getResult(); + osg::Node* getNewNode(osg::Node& node) + { + return getNewNode(&node); + } + osg::Node* getNewNode(osg::Node* node); + bool recordNewNode(osg::Node* oldNode, osg::Node* newNode); + osg::NodeList& getResults() { return _childStack.back(); } +protected: + std::vector _childStack; + typedef std::map, osg::ref_ptr > NodeMap; + NodeMap _visited; +}; + +template +T* SplicingVisitor::copyIfNeeded(T& node, const osg::NodeList& children) +{ + bool copyNeeded = false; + if (node.getNumChildren() == children.size()) { + for (std::size_t i = 0; i < children.size(); ++i) + if (node.getChild(i) != children[i].get()) { + copyNeeded = true; + break; + } + } else { + copyNeeded = true; + } + if (copyNeeded) + return copy(node, children); + else + return &node; +} + +template +T* SplicingVisitor::copy(T& node, const osg::NodeList& children) +{ + T* result = osg::clone(&node, osg::CopyOp::SHALLOW_COPY); + result->removeChildren(0, result->getNumChildren()); + for (osg::NodeList::const_iterator itr = children.begin(), + end = children.end(); + itr != end; + ++itr) + result->addChild(itr->get()); + return result; +} +} +#endif diff --git a/simgear/scene/util/StateAttributeFactory.cxx b/simgear/scene/util/StateAttributeFactory.cxx index 6e121c07..76124da0 100644 --- a/simgear/scene/util/StateAttributeFactory.cxx +++ b/simgear/scene/util/StateAttributeFactory.cxx @@ -64,6 +64,17 @@ StateAttributeFactory::StateAttributeFactory() _whiteTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); _whiteTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); _whiteTexture->setDataVariance(osg::Object::STATIC); + // And now the transparent texture + dummyImage = new osg::Image; + dummyImage->allocateImage(1, 1, 1, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE); + imageBytes = dummyImage->data(0, 0); + imageBytes[0] = 255; + imageBytes[1] = 0; + _transparentTexture = new osg::Texture2D; + _transparentTexture->setImage(dummyImage); + _transparentTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + _transparentTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + _transparentTexture->setDataVariance(osg::Object::STATIC); _white = new Vec4Array(1); (*_white)[0].set(1.0f, 1.0f, 1.0f, 1.0f); _white->setDataVariance(Object::STATIC); diff --git a/simgear/scene/util/StateAttributeFactory.hxx b/simgear/scene/util/StateAttributeFactory.hxx index 18b1a59d..0076fa51 100644 --- a/simgear/scene/util/StateAttributeFactory.hxx +++ b/simgear/scene/util/StateAttributeFactory.hxx @@ -57,6 +57,11 @@ public: osg::Texture2D* getWhiteTexture() { return _whiteTexture.get(); } // White color osg::Vec4Array* getWhiteColor() {return _white.get(); } + // A white, completely transparent texture + osg::Texture2D* getTransparentTexture() + { + return _transparentTexture.get(); + } // cull front and back facing polygons osg::CullFace* getCullFaceFront() { return _cullFaceFront.get(); } osg::CullFace* getCullFaceBack() { return _cullFaceBack.get(); } @@ -70,6 +75,7 @@ protected: osg::ref_ptr _standardBlendFunc; osg::ref_ptr _standardTexEnv; osg::ref_ptr _whiteTexture; + osg::ref_ptr _transparentTexture; osg::ref_ptr _white; osg::ref_ptr _cullFaceFront; osg::ref_ptr _cullFaceBack; diff --git a/simgear/scene/util/UpdateOnceCallback.cxx b/simgear/scene/util/UpdateOnceCallback.cxx new file mode 100644 index 00000000..7130b507 --- /dev/null +++ b/simgear/scene/util/UpdateOnceCallback.cxx @@ -0,0 +1,39 @@ +// UpdateOnceCallback.hxx +// +// Copyright (C) 2009 Tim Moore timoore@redhat.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + +#include "UpdateOnceCallback.hxx" + +#include + +namespace simgear +{ +using namespace osg; + +void UpdateOnceCallback::operator()(Node* node, NodeVisitor* nv) +{ + doUpdate(node, nv); + node->removeUpdateCallback(this); + // The callback could be deleted now. +} + +void UpdateOnceCallback::doUpdate(Node* node, NodeVisitor* nv) +{ + traverse(node, nv); +} +} diff --git a/simgear/scene/util/UpdateOnceCallback.hxx b/simgear/scene/util/UpdateOnceCallback.hxx new file mode 100644 index 00000000..04034844 --- /dev/null +++ b/simgear/scene/util/UpdateOnceCallback.hxx @@ -0,0 +1,44 @@ +// UpdateOnceCallback.hxx +// +// Copyright (C) 2009 Tim Moore timoore@redhat.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 59 Temple Place - Suite 330, +// Boston, MA 02111-1307, USA. + +#ifndef SIMGEAR_UPDATEONCECALLBACK_HXX +#define SIMGEAR_UPDATEONCECALLBACK_HXX 1 +#include + +namespace simgear +{ +class UpdateOnceCallback : public osg::NodeCallback +{ +public: + UpdateOnceCallback() {} + UpdateOnceCallback(const UpdateOnceCallback& nc, const osg::CopyOp& copyop) + : osg::NodeCallback(nc, copyop) + { + } + + META_Object(simgear,UpdateOnceCallback); + + virtual void doUpdate(osg::Node* node, osg::NodeVisitor* nv); + /** + * Do not override; use doUpdate instead! + */ + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv); +}; +} +#endif diff --git a/simgear/structure/OSGUtils.hxx b/simgear/structure/OSGUtils.hxx index 21c5d698..557339d0 100644 --- a/simgear/structure/OSGUtils.hxx +++ b/simgear/structure/OSGUtils.hxx @@ -20,8 +20,9 @@ #ifndef SIMGEAR_OSGUTILS_HXX #define SIMGEAR_OSGUTILS_HXX 1 -#include #include +#include +#include namespace simgear { @@ -99,59 +100,280 @@ T* clone_ref(const osg::ref_ptr& object, return static_cast(object->clone(copyop)); } -template -class BackRefInsertIterator - : public boost::iterator_facade, - BackRefInsertIterator, - boost::incrementable_traversal_tag - > +} + +namespace osg { -public: - typedef typename Container::value_type::element_type* PtrType; - BackRefInsertIterator() : _container(0) {} - explicit BackRefInsertIterator(Container& container) - : _container(&container) - { - } +class AlphaFunc; +class BlendColor; +class BlendEquation; +class BlendFunc; +class ClampColor; +class ColorMask; +class ColorMatrix; +class CullFace; +class Depth; +class Fog; +class FragmentProgram; +class FrontFace; +class LightModel; +class LineStipple; +class LineWidth; +class LogicOp; +class Material; +class Multisample; +class Point; +class PointSprite; +class PolygonMode; +class PolygonOffset; +class PolygonStipple; +class Program; +class Scissor; +class ShadeModel; +class Stencil; +class StencilTwoSided; +class TexEnv; +class TexEnvCombine; +class TexEnvFilter; +class TexGen; +class TexMat; +class Texture1D; +class Texture2D; +class Texture2DArray; +class Texture3D; +class TextureCubeMap; +class TextureRectangle; +class VertexProgram; +class Viewport; +} - BackRefInsertIterator& - operator=(const PtrType ptr) - { - _container->push_back(ptr); - return *this; - } - -private: - friend class boost::iterator_core_access; +namespace simgear +{ +namespace osgutils +{ +using namespace osg; - void increment() - { - } - - BackRefInsertIterator& dereference() - { - return *this; - } - - BackRefInsertIterator& dereference() const - { - return const_cast(*this); - } - - bool equal(const BackRefInsertIterator& rhs) - { - return _container == rhs._container; - } - - Container* _container; +template +struct TypeHolder +{ + static const StateAttribute::Type type = T; }; +template struct AttributeType; +template struct TexAttributeType; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + + +template<> +struct AttributeType // Conflicts with Xlib + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct TexAttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +// TexEnvCombine is not a subclass of TexEnv, so we can't do a +// typesafe access of the attribute. +#if 0 +template<> +struct TexAttributeType + : public TypeHolder +{}; + +template<> +struct TexAttributeType + : public TypeHolder +{}; +#endif + +template<> +struct TexAttributeType + : public TypeHolder +{}; + +template<> +struct TexAttributeType + : public TypeHolder +{}; + +template<> +struct TexAttributeType + : public TypeHolder +{}; + +template<> +struct TexAttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; + +template<> +struct AttributeType + : public TypeHolder +{}; +} // namespace osgutils + +template +inline AT* getStateAttribute(osg::StateSet* ss) +{ + return static_cast(ss->getAttribute(osgutils::AttributeType::type)); +} + +template +inline const AT* getStateAttribute(const osg::StateSet* ss) +{ + return static_cast(ss->getAttribute(osgutils::AttributeType::type)); +} + +template +inline AT* getStateAttribute(unsigned int unit, osg::StateSet* ss) +{ + return static_cast(ss->getTextureAttribute(unit, osgutils::TexAttributeType + ::type)); +} + +template +inline const AT* getStateAttribute(unsigned int unit, const osg::StateSet* ss) +{ + return static_cast(ss->getTextureAttribute(unit, + osgutils::TexAttributeType + ::type)); +} +} // namespace simgear -template -inline BackRefInsertIterator -backRefInsertIterator(Container& container) -{ - return BackRefInsertIterator(container); -} -} #endif