blob: fe9e0fbfe8f0bf7b3852c9f73e947c3decfc5a41 [file] [log] [blame]
/*
* 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;
}