/* * Copyright 2006 Sony Computer Entertainment Inc. * * Licensed under the MIT Open Source License, for details please see license.txt or the website * http://www.opensource.org/licenses/mit-license.php * */ #include <list> #include <vector> #include <sstream> #include <algorithm> #include <dae/daeSIDResolver.h> #include <dae/daeIDRef.h> #include <dae/daeAtomicType.h> #include <dae/daeMetaAttribute.h> #include <dae/daeMetaElement.h> #include <dae/daeURI.h> #include <dom/domTypes.h> #include <dom/domConstants.h> #include <dae/daeDocument.h> #include <dae/daeDatabase.h> #include <dae/daeUtils.h> #include <dom/domSource.h> using namespace std; namespace { template<typename T> T nextIter(const T& iter) { T next = iter; return ++next; } template<typename T> T moveIter(const T& iter, int n) { T result = iter; advance(result, n); return result; } // Implements a breadth-first sid search by starting at the container element and // traversing downward through the element tree. daeElement* findSidTopDown(daeElement* container, const string& sid, const string& profile) { if (!container) return NULL; vector<daeElement*> elts, matchingElts; elts.push_back(container); for (size_t i = 0; i < elts.size(); i++) { daeElement* elt = elts[i]; // Bail if we're looking for an element in a different profile if (!profile.empty()) { if (strcmp(elt->getElementName(), COLLADA_ELEMENT_TECHNIQUE_COMMON) == 0) continue; if (strcmp(elt->getElementName(), COLLADA_ELEMENT_TECHNIQUE) == 0 && profile != elt->getAttribute("profile")) continue; } // See if this is a matching element if (elt->getAttribute("sid") == sid) return elt; else { // Add the children to the list of elements to check daeElementRefArray children; elt->getChildren(children); for (size_t j = 0; j < children.getCount(); j++) elts.push_back(children[j]); } } return NULL; } // Returns the distance between an element and an ancestor of the element. If 'container // isn't an ancestor of 'elt', or if 'elt' is in a profile that doesn't match 'profile' // UINT_MAX is returned. unsigned int computeDistance(daeElement* container, daeElement* elt, const string& profile) { if (!container || !elt) return UINT_MAX; unsigned int distance = 0; do { // Bail if we're looking for an element in a different profile if (!profile.empty()) { if (strcmp(elt->getElementName(), COLLADA_ELEMENT_TECHNIQUE_COMMON) == 0) return UINT_MAX; if (strcmp(elt->getElementName(), COLLADA_ELEMENT_TECHNIQUE) == 0 && profile != elt->getAttribute("profile")) return UINT_MAX; } if (elt == container) return distance; distance++; } while ((elt = elt->getParentElement()) != NULL); return UINT_MAX; } // Implements a breadth-first sid search by using the database to find all elements // matching 'sid', then finding the element closest to 'container'. daeElement* findSidBottomUp(daeElement* container, const string& sid, const string& profile) { if (!container || !container->getDocument()) return NULL; // Get the elements with a matching sid vector<daeElement*> elts; container->getDocument()->getDAE()->getDatabase()->sidLookup(sid, elts, container->getDocument()); // Compute the distance from each matching element to the container element unsigned int minDistance = UINT_MAX; daeElement* closestElt = NULL; for (size_t i = 0; i < elts.size(); i++) { unsigned int distance = computeDistance(container, elts[i], profile); if (distance < minDistance) { minDistance = distance; closestElt = elts[i]; } } return closestElt; } daeElement* findID(daeElement* elt, const string& id, const string& profile) { return elt ? elt->getDAE()->getDatabase()->idLookup(id, elt->getDocument()) : NULL; } void buildString(const list<string>::iterator& begin, const list<string>::iterator& end, string& result) { ostringstream stream; for (list<string>::iterator iter = begin; iter != end; iter++) stream << *iter; result = stream.str(); } // Finds an element with a matching ID or sid (depending on the 'finder' function) // passed in. First it tries to resolve the whole ID/sid, then it tries to resolve // successively smaller parts. For example, consider this sid ref: "my.sid.ref". // First this function will try to resolve "my.sid.ref" entirely, then if that // fails it'll try to resolve "my.sid.", "my.sid", "my.", and "my", in that order. // The part that wasn't matched is returned in the 'remainingPart' parameter. daeElement* findWithDots(daeElement* container, const string& s, const string& profile, daeElement* (*finder)(daeElement*, const string&, const string&), list<string>& remainingPart) { remainingPart.clear(); // First see if the whole thing resolves correctly if (daeElement* result = finder(container, s, profile)) return result; // It didn't resolve. Let's tokenize it by '.'s and see if we can resolve a // portion of it. cdom::tokenize(s, ".", remainingPart, true); if (remainingPart.size() == 1) return NULL; // There were no '.'s, so the result won't be any different list<string>::iterator iter = moveIter(remainingPart.end(), -1); for (int i = int(remainingPart.size())-1; i >= 1; i--, iter--) { string substr; buildString(remainingPart.begin(), iter, substr); if (daeElement* result = finder(container, substr, profile)) { // Remove the part we matched against from the list remainingPart.erase(remainingPart.begin(), iter); return result; } } remainingPart.clear(); return NULL; } daeSidRef::resolveData resolveImpl(const daeSidRef& sidRef) { if (sidRef.sidRef.empty() || !sidRef.refElt) return daeSidRef::resolveData(); daeSidRef::resolveData result; string separators = "/()"; list<string> tokens; cdom::tokenize(sidRef.sidRef, separators, /* out */ tokens, true); list<string>::iterator tok = tokens.begin(); // The first token should be either an ID or a '.' to indicate // that we should start the search from the container element. if (tok == tokens.end()) return daeSidRef::resolveData(); list<string> remainingPart; if (*tok == ".") { result.elt = sidRef.refElt; tok++; } else { // Try to resolve it as an ID result.elt = findWithDots(sidRef.refElt, *tok, sidRef.profile, findID, remainingPart); if (result.elt) { if (!remainingPart.empty()) { // Insert the "remaining part" from the ID resolve into our list of tokens tokens.erase(tokens.begin()); tokens.splice(tokens.begin(), remainingPart); tok = tokens.begin(); } else tok++; } } if (!result.elt) return daeSidRef::resolveData(); // Next we have an optional list of SIDs, each one separated by "/". Once we hit one of "()", // we know we're done with the SID section. for (; tok != tokens.end() && *tok == "/"; tok++) { tok++; // Read the '/' if (tok == tokens.end()) return daeSidRef::resolveData(); // Find the element matching the SID result.elt = findWithDots(result.elt, *tok, sidRef.profile, findSidTopDown, remainingPart); if (!result.elt) return daeSidRef::resolveData(); if (!remainingPart.empty()) { list<string>::iterator tmp = tok; tok--; tokens.splice(tmp, remainingPart); tokens.erase(tmp); } } // Now we want to parse the member selection tokens. It can either be // (a) '.' followed by a string representing the member to access // (b) '(x)' where x is a number, optionally followed by another '(x)' // Anything else is an error. string member; bool haveArrayIndex1 = false, haveArrayIndex2 = false; int arrayIndex1 = -1, arrayIndex2 = -1; if (tok != tokens.end()) { if (*tok == ".") { tok++; if (tok == tokens.end()) return daeSidRef::resolveData(); member = *tok; tok++; } else if (*tok == "(") { tok++; if (tok == tokens.end()) return daeSidRef::resolveData(); istringstream stream(*tok); stream >> arrayIndex1; haveArrayIndex1 = true; if (!stream.good() && !stream.eof()) return daeSidRef::resolveData(); tok++; if (tok == tokens.end() || *tok != ")") return daeSidRef::resolveData(); tok++; if (tok != tokens.end() && *tok == "(") { tok++; if (tok == tokens.end()) return daeSidRef::resolveData(); stream.clear(); stream.str(*tok); stream >> arrayIndex2; haveArrayIndex2 = true; if (!stream.good() && !stream.eof()) return daeSidRef::resolveData(); tok++; if (tok == tokens.end() || *tok != ")") return daeSidRef::resolveData(); tok++; } } } // We shouldn't have any tokens left. If we do it's an error. if (tok != tokens.end()) return daeSidRef::resolveData(); // At this point we've parsed a correctly formatted SID reference. The only thing left is to resolve // the member selection portion of the SID ref. First, see if the resolved element has a float array we // can use. if (result.elt->typeID() == domSource::ID()) { if (domFloat_array* floatArray = ((domSource*)result.elt)->getFloat_array()) result.array = (daeDoubleArray*)floatArray->getCharDataObject()->get(floatArray); } else { daeMetaAttribute *ma = result.elt->getCharDataObject(); if ( ma != NULL ) { if ( ma->isArrayAttribute() && ma->getType()->getTypeEnum() == daeAtomicType::DoubleType ) { result.array = (daeDoubleArray*)ma->get( result.elt ); } } } if( result.array ) { // We have an array to use for indexing. Let's see if the SID ref uses member selection. if (!member.empty()) { // Do member lookup based on the constants defined in the COMMON profile if (member == "ANGLE") { result.scalar = &(result.array->get(3)); } else if (member.length() == 1) { switch(member[0]) { case 'X': case 'R': case 'U': case 'S': result.scalar = &(result.array->get(0)); break; case 'Y': case 'G': case 'V': case 'T': result.scalar = &(result.array->get(1)); break; case 'Z': case 'B': case 'P': result.scalar = &(result.array->get(2)); break; case 'W': case 'A': case 'Q': result.scalar = &(result.array->get(3)); break; }; } } else if (haveArrayIndex1) { // Use the indices to lookup a value in the array if (haveArrayIndex2 && result.array->getCount() == 16) { // We're doing a matrix lookup. Make sure the index is valid. int i = arrayIndex1*4 + arrayIndex2; if (i >= 0 && i < int(result.array->getCount())) result.scalar = &(result.array->get(i)); } else { // Vector lookup. Make sure the index is valid. if (arrayIndex1 >= 0 && arrayIndex1 < int(result.array->getCount())) result.scalar = &(result.array->get(arrayIndex1)); } } } // If we tried to do member selection but we couldn't resolve it to a doublePtr, fail. if ((!member.empty() || haveArrayIndex1) && result.scalar == NULL) return daeSidRef::resolveData(); // SID resolution was successful. return result; } } // namespace { daeSidRef::resolveData::resolveData() : elt(NULL), array(NULL), scalar(NULL) { } daeSidRef::resolveData::resolveData(daeElement* elt, daeDoubleArray* array, daeDouble* scalar) : elt(elt), array(array), scalar(scalar) { } daeSidRef::daeSidRef() : refElt(NULL) { } daeSidRef::daeSidRef(const string& sidRef, daeElement* referenceElt, const string& profile) : sidRef(sidRef), refElt(referenceElt), profile(profile) { } bool daeSidRef::operator<(const daeSidRef& other) const { if (refElt != other.refElt) return refElt < other.refElt; if (sidRef != other.sidRef) return sidRef < other.sidRef; return profile < other.profile; } daeSidRef::resolveData daeSidRef::resolve() { if (!refElt) return daeSidRef::resolveData(); // First check the cache daeSidRef::resolveData result = refElt->getDAE()->getSidRefCache().lookup(*this); if (result.elt) return result; // Try to resolve as an effect-style sid ref by prepending "./" to the sid ref. // If that fails, try resolving as an animation-style sid ref, where the first part is an ID. result = resolveImpl(daeSidRef(string("./") + sidRef, refElt, profile)); if (!result.elt) result = resolveImpl(*this); if (result.elt) // Add the result to the cache refElt->getDAE()->getSidRefCache().add(*this, result); return result; } daeSIDResolver::daeSIDResolver( daeElement *container, daeString target, daeString profile ) : container(NULL) { setContainer(container); setTarget(target); setProfile(profile); } daeString daeSIDResolver::getTarget() const { return target.empty() ? NULL : target.c_str(); } void daeSIDResolver::setTarget( daeString t ) { target = t ? t : ""; } daeString daeSIDResolver::getProfile() const { return profile.empty() ? NULL : profile.c_str(); } void daeSIDResolver::setProfile( daeString p ) { profile = p ? p : ""; } daeElement* daeSIDResolver::getContainer() const { return container; } void daeSIDResolver::setContainer(daeElement* element) { container = element; } daeSIDResolver::ResolveState daeSIDResolver::getState() const { if (target.empty()) return target_empty; daeSidRef::resolveData result = daeSidRef(target, container, profile).resolve(); if (!result.elt) return sid_failed_not_found; if (result.scalar) return sid_success_double; if (result.array) return sid_success_array; return sid_success_element; } daeElement* daeSIDResolver::getElement() { return daeSidRef(target, container, profile).resolve().elt; } daeDoubleArray *daeSIDResolver::getDoubleArray() { return daeSidRef(target, container, profile).resolve().array; } daeDouble *daeSIDResolver::getDouble() { return daeSidRef(target, container, profile).resolve().scalar; } daeSidRefCache::daeSidRefCache() : hitCount(0), missCount(0) { } daeSidRef::resolveData daeSidRefCache::lookup(const daeSidRef& sidRef) { map<daeSidRef, daeSidRef::resolveData>::iterator iter = lookupTable.find(sidRef); if (iter != lookupTable.end()) { hitCount++; return iter->second; } missCount++; return daeSidRef::resolveData(); } void daeSidRefCache::add(const daeSidRef& sidRef, const daeSidRef::resolveData& result) { lookupTable[sidRef] = result; } void daeSidRefCache::clear() { lookupTable.clear(); hitCount = missCount = 0; } bool daeSidRefCache::empty() { return lookupTable.empty(); } int daeSidRefCache::misses() { return missCount; } int daeSidRefCache::hits() { return hitCount; }