Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,225 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "ts/loader/tsShapeLoader.h"
#include "ts/collada/colladaAppMaterial.h"
#include "ts/collada/colladaUtils.h"
#include "ts/tsMaterialList.h"
#include "materials/materialManager.h"
using namespace ColladaUtils;
String cleanString(const String& str)
{
String cleanStr(str);
// Replace invalid characters with underscores
const String badChars(" -,.+=*/");
for (String::SizeType i = 0; i < badChars.length(); i++)
cleanStr.replace(badChars[i], '_');
// Prefix with an underscore if string starts with a number
if ((cleanStr[0] >= '0') && (cleanStr[0] <= '9'))
cleanStr.insert(0, '_');
return cleanStr;
}
//------------------------------------------------------------------------------
ColladaAppMaterial::ColladaAppMaterial(const char* matName)
: mat(0),
effect(0),
effectExt(0)
{
name = matName;
// Set some defaults
flags |= TSMaterialList::S_Wrap;
flags |= TSMaterialList::T_Wrap;
diffuseColor = ColorF::ONE;
specularColor = ColorF::ONE;
specularPower = 8.0f;
doubleSided = false;
}
ColladaAppMaterial::ColladaAppMaterial(const domMaterial *pMat)
: mat(pMat),
diffuseColor(ColorF::ONE),
specularColor(ColorF::ONE),
specularPower(8.0f),
doubleSided(false)
{
// Get the effect element for this material
effect = daeSafeCast<domEffect>(mat->getInstance_effect()->getUrl().getElement());
effectExt = new ColladaExtension_effect(effect);
// Get the <profile_COMMON>, <diffuse> and <specular> elements
const domProfile_COMMON* commonProfile = ColladaUtils::findEffectCommonProfile(effect);
const domCommon_color_or_texture_type_complexType* domDiffuse = findEffectDiffuse(effect);
const domCommon_color_or_texture_type_complexType* domSpecular = findEffectSpecular(effect);
// Wrap flags
if (effectExt->wrapU)
flags |= TSMaterialList::S_Wrap;
if (effectExt->wrapV)
flags |= TSMaterialList::T_Wrap;
// Set material attributes
if (commonProfile) {
F32 transparency = 0.0f;
if (commonProfile->getTechnique()->getConstant()) {
const domProfile_COMMON::domTechnique::domConstant* constant = commonProfile->getTechnique()->getConstant();
diffuseColor.set(1.0f, 1.0f, 1.0f, 1.0f);
resolveColor(constant->getReflective(), &specularColor);
resolveFloat(constant->getReflectivity(), &specularPower);
resolveTransparency(constant, &transparency);
}
else if (commonProfile->getTechnique()->getLambert()) {
const domProfile_COMMON::domTechnique::domLambert* lambert = commonProfile->getTechnique()->getLambert();
resolveColor(lambert->getDiffuse(), &diffuseColor);
resolveColor(lambert->getReflective(), &specularColor);
resolveFloat(lambert->getReflectivity(), &specularPower);
resolveTransparency(lambert, &transparency);
}
else if (commonProfile->getTechnique()->getPhong()) {
const domProfile_COMMON::domTechnique::domPhong* phong = commonProfile->getTechnique()->getPhong();
resolveColor(phong->getDiffuse(), &diffuseColor);
resolveColor(phong->getSpecular(), &specularColor);
resolveFloat(phong->getShininess(), &specularPower);
resolveTransparency(phong, &transparency);
}
else if (commonProfile->getTechnique()->getBlinn()) {
const domProfile_COMMON::domTechnique::domBlinn* blinn = commonProfile->getTechnique()->getBlinn();
resolveColor(blinn->getDiffuse(), &diffuseColor);
resolveColor(blinn->getSpecular(), &specularColor);
resolveFloat(blinn->getShininess(), &specularPower);
resolveTransparency(blinn, &transparency);
}
// Normalize specularPower (1-128). Values > 1 are assumed to be
// already normalized.
if (specularPower <= 1.0f)
specularPower *= 128;
specularPower = mClampF(specularPower, 1.0f, 128.0f);
// Set translucency
if (transparency != 0.0f) {
flags |= TSMaterialList::Translucent;
if (transparency > 1.0f) {
flags |= TSMaterialList::Additive;
diffuseColor.alpha = transparency - 1.0f;
}
else if (transparency < 0.0f) {
flags |= TSMaterialList::Subtractive;
diffuseColor.alpha = -transparency;
}
else {
diffuseColor.alpha = transparency;
}
}
else
diffuseColor.alpha = 1.0f;
}
// Double-sided flag
doubleSided = effectExt->double_sided;
// Get the paths for the various textures => Collada indirection at its finest!
// <texture>.<newparam>.<sampler2D>.<source>.<newparam>.<surface>.<init_from>.<image>.<init_from>
diffuseMap = getSamplerImagePath(effect, getTextureSampler(effect, domDiffuse));
specularMap = getSamplerImagePath(effect, getTextureSampler(effect, domSpecular));
normalMap = getSamplerImagePath(effect, effectExt->bumpSampler);
// Set the material name
name = ColladaUtils::getOptions().matNamePrefix;
if ( ColladaUtils::getOptions().useDiffuseNames )
{
Torque::Path diffusePath( diffuseMap );
name += diffusePath.getFileName();
}
else
{
name += _GetNameOrId(mat);
}
}
void ColladaAppMaterial::resolveFloat(const domCommon_float_or_param_type* value, F32* dst)
{
if (value && value->getFloat()) {
*dst = value->getFloat()->getValue();
}
}
void ColladaAppMaterial::resolveColor(const domCommon_color_or_texture_type* value, ColorF* dst)
{
if (value && value->getColor()) {
dst->red = value->getColor()->getValue()[0];
dst->green = value->getColor()->getValue()[1];
dst->blue = value->getColor()->getValue()[2];
dst->alpha = value->getColor()->getValue()[3];
}
}
// Generate a new Material object
Material *ColladaAppMaterial::createMaterial(const Torque::Path& path) const
{
// The filename and material name are used as TorqueScript identifiers, so
// clean them up first
String cleanFile = cleanString(TSShapeLoader::getShapePath().getFileName());
String cleanName = cleanString(getName());
// Prefix the material name with the filename (if not done already by TSShapeConstructor prefix)
if (!cleanName.startsWith(cleanFile))
cleanName = cleanFile + "_" + cleanName;
// Determine the blend operation for this material
Material::BlendOp blendOp = (flags & TSMaterialList::Translucent) ? Material::LerpAlpha : Material::None;
if (flags & TSMaterialList::Additive)
blendOp = Material::Add;
else if (flags & TSMaterialList::Subtractive)
blendOp = Material::Sub;
// Create the Material definition
const String oldScriptFile = Con::getVariable("$Con::File");
Con::setVariable("$Con::File", path.getFullPath()); // modify current script path so texture lookups are correct
Material *newMat = MATMGR->allocateAndRegister( cleanName, getName() );
Con::setVariable("$Con::File", oldScriptFile); // restore script path
newMat->mDiffuseMapFilename[0] = diffuseMap;
newMat->mNormalMapFilename[0] = normalMap;
newMat->mSpecularMapFilename[0] = specularMap;
newMat->mDiffuse[0] = diffuseColor;
newMat->mSpecular[0] = specularColor;
newMat->mSpecularPower[0] = specularPower;
newMat->mDoubleSided = doubleSided;
newMat->mTranslucent = (bool)(flags & TSMaterialList::Translucent);
newMat->mTranslucentBlendOp = blendOp;
return newMat;
}

View file

@ -0,0 +1,89 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_APP_MATERIAL_H_
#define _COLLADA_APP_MATERIAL_H_
#ifndef _APPMATERIAL_H_
#include "ts/loader/appMaterial.h"
#endif
#ifndef _COLLADA_EXTENSIONS_H_
#include "ts/collada/colladaExtensions.h"
#endif
class Material;
class ColladaAppMaterial : public AppMaterial
{
public:
const domMaterial* mat; ///< Collada <material> element
domEffect* effect; ///< Collada <effect> element
ColladaExtension_effect* effectExt; ///< effect extension
String name; ///< Name of this material (cleaned)
// Settings extracted from the Collada file, and optionally saved to materials.cs
String diffuseMap;
String normalMap;
String specularMap;
ColorF diffuseColor;
ColorF specularColor;
F32 specularPower;
bool doubleSided;
ColladaAppMaterial(const char* matName);
ColladaAppMaterial(const domMaterial* pMat);
~ColladaAppMaterial() { delete effectExt; }
String getName() const { return name; }
void resolveFloat(const domCommon_float_or_param_type* value, F32* dst);
void resolveColor(const domCommon_color_or_texture_type* value, ColorF* dst);
// Determine the material transparency
template<class T> void resolveTransparency(const T shader, F32* dst)
{
// Start out by getting the <transparency> value
*dst = 1.0f;
resolveFloat(shader->getTransparency(), dst);
// Multiply the transparency by the transparent color
ColorF transColor(1.0f, 1.0f, 1.0f, 1.0f);
if (shader->getTransparent() && shader->getTransparent()->getColor()) {
const domCommon_color_or_texture_type::domColor* color = shader->getTransparent()->getColor();
transColor.set(color->getValue()[0], color->getValue()[1], color->getValue()[2], color->getValue()[3]);
}
if (!shader->getTransparent() || (shader->getTransparent()->getOpaque() == FX_OPAQUE_ENUM_A_ONE)) {
// multiply by alpha value and invert (so 1.0 is fully opaque)
*dst = 1.0f - (*dst * transColor.alpha);
}
else {
// multiply by average of the RGB values
F32 avg = (transColor.red + transColor.blue + transColor.green) / 3;
*dst *= avg;
}
}
Material *createMaterial(const Torque::Path& path) const;
};
#endif // _COLLADA_APP_MATERIAL_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,232 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_APPMESH_H_
#define _COLLADA_APPMESH_H_
#ifndef _TDICTIONARY_H_
#include "core/tDictionary.h"
#endif
#ifndef _APPMESH_H_
#include "ts/loader/appMesh.h"
#endif
#ifndef _TSSHAPELOADER_H_
#include "ts/loader/tsShapeLoader.h"
#endif
#ifndef _COLLADA_APPNODE_H_
#include "ts/collada/colladaAppNode.h"
#endif
#ifndef _COLLADA_EXTENSIONS_H_
#include "ts/collada/colladaExtensions.h"
#endif
//-----------------------------------------------------------------------------
// Torque unifies the vert position, normal and UV values, so that a single index
// uniquely identifies all 3 elements. A triangle then contains just 3 indices,
// and from that we can get the 3 positions, 3 normals and 3 UVs.
//
// for i=1:3
// index = indices[triangle.start + i]
// points[index], normals[index], uvs[index]
//
// Collada does not use unified vertex streams, (each triangle needs 9 indices),
// so this structure is used to map a single VertTuple index to 3 indices into
// the Collada streams. The Collada (and Torque) primitive index is also stored
// because the Collada document may use different streams for different primitives.
//
// For morph geometry, we can use the same array of VertTuples to access the base
// AND all of the target geometries because they MUST have the same topology.
struct VertTuple
{
S32 prim, vertex, normal, color, uv, uv2;
Point3F dataVertex, dataNormal;
ColorI dataColor;
Point2F dataUV, dataUV2;
VertTuple(): prim(-1), vertex(-1), normal(-1), color(-1), uv(-1), uv2(-1) {}
bool operator==(const VertTuple& p) const
{
return dataVertex == p.dataVertex &&
dataColor == p.dataColor &&
dataNormal == p.dataNormal &&
dataUV == p.dataUV &&
dataUV2 == p.dataUV2;
}
};
class ColladaAppMesh : public AppMesh
{
typedef AppMesh Parent;
protected:
class ColladaAppNode* appNode; ///< Pointer to the node that owns this mesh
const domInstance_geometry* instanceGeom;
const domInstance_controller* instanceCtrl;
ColladaExtension_geometry* geomExt; ///< geometry extension
Vector<VertTuple> vertTuples; ///<
Map<StringTableEntry,U32> boundMaterials; ///< Local map of symbols to materials
static bool fixedSizeEnabled; ///< Set to true to fix the detail size to a particular value for all geometry
static S32 fixedSize; ///< The fixed detail size value for all geometry
//-----------------------------------------------------------------------
/// Get the morph controller for this mesh (if any)
const domMorph* getMorph()
{
if (instanceCtrl) {
const domController* ctrl = daeSafeCast<domController>(instanceCtrl->getUrl().getElement());
if (ctrl && ctrl->getSkin())
ctrl = daeSafeCast<domController>(ctrl->getSkin()->getSource().getElement());
return ctrl ? ctrl->getMorph() : NULL;
}
return NULL;
}
S32 addMaterial(const char* symbol);
bool checkGeometryType(const daeElement* element);
void getPrimitives(const domGeometry* geometry);
void getVertexData( const domGeometry* geometry, F32 time, const MatrixF& objectOffset,
Vector<Point3F>& points, Vector<Point3F>& norms, Vector<ColorI>& colors,
Vector<Point2F>& uvs, Vector<Point2F>& uv2s, bool appendValues);
void getMorphVertexData( const domMorph* morph, F32 time, const MatrixF& objectOffset,
Vector<Point3F>& points, Vector<Point3F>& norms, Vector<ColorI>& colors,
Vector<Point2F>& uvs, Vector<Point2F>& uv2s );
public:
ColladaAppMesh(const domInstance_geometry* instance, ColladaAppNode* node);
ColladaAppMesh(const domInstance_controller* instance, ColladaAppNode* node);
~ColladaAppMesh()
{
delete geomExt;
}
static void fixDetailSize(bool fixed, S32 size=2)
{
fixedSizeEnabled = fixed;
fixedSize = size;
}
/// Get the name of this mesh
///
/// @return A string containing the name of this mesh
const char *getName(bool allowFixed=true);
//-----------------------------------------------------------------------
/// Get a floating point property value
///
/// @param propName Name of the property to get
/// @param defaultVal Reference to variable to hold return value
///
/// @return True if a value was set, false if not
bool getFloat(const char *propName, F32 &defaultVal)
{
return appNode->getFloat(propName,defaultVal);
}
/// Get an integer property value
///
/// @param propName Name of the property to get
/// @param defaultVal Reference to variable to hold return value
///
/// @return True if a value was set, false if not
bool getInt(const char *propName, S32 &defaultVal)
{
return appNode->getInt(propName,defaultVal);
}
/// Get a boolean property value
///
/// @param propName Name of the property to get
/// @param defaultVal Reference to variable to hold return value
///
/// @return True if a value was set, false if not
bool getBool(const char *propName, bool &defaultVal)
{
return appNode->getBool(propName,defaultVal);
}
/// Return true if this mesh is a skin
bool isSkin()
{
if (instanceCtrl) {
const domController* ctrl = daeSafeCast<domController>(instanceCtrl->getUrl().getElement());
if (ctrl && ctrl->getSkin() &&
(ctrl->getSkin()->getVertex_weights()->getV()->getValue().getCount() > 0))
return true;
}
return false;
}
/// Get the skin data: bones, vertex weights etc
void lookupSkinData();
/// Check if the mesh visibility is animated
///
/// @param appSeq Start/end time to check
///
/// @return True if the mesh visibility is animated, false if not
bool animatesVis(const AppSequence* appSeq);
/// Check if the material used by this mesh is animated
///
/// @param appSeq Start/end time to check
///
/// @return True if the material is animated, false if not
bool animatesMatFrame(const AppSequence* appSeq);
/// Check if the mesh is animated
///
/// @param appSeq Start/end time to check
///
/// @return True if the mesh is animated, false if not
bool animatesFrame(const AppSequence* appSeq);
/// Generate the vertex, normal and triangle data for the mesh.
///
/// @param time Time at which to generate the mesh data
/// @param objectOffset Transform to apply to the generated data (bounds transform)
void lockMesh(F32 time, const MatrixF& objectOffset);
/// Get the transform of this mesh at a certain time
///
/// @param time Time at which to get the transform
///
/// @return The mesh transform at the specified time
MatrixF getMeshTransform(F32 time);
/// Get the visibility of this mesh at a certain time
///
/// @param time Time at which to get visibility info
///
/// @return Visibility from 0 (invisible) to 1 (opaque)
F32 getVisValue(F32 time);
};
#endif // _COLLADA_APPMESH_H_

View file

@ -0,0 +1,260 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#ifdef _MSC_VER
#pragma warning(disable : 4706) // disable warning about assignment within conditional
#endif
#include "ts/loader/appSequence.h"
#include "ts/collada/colladaExtensions.h"
#include "ts/collada/colladaAppNode.h"
#include "ts/collada/colladaAppMesh.h"
#include "ts/collada/colladaAppMesh.h"
#include "core/stringTable.h"
// Trim leading and trailing whitespace from the first word in the string
// Note that the string is modified.
static char* TrimFirstWord(char* str)
{
char* value = str;
// Trim leading whitespace
while ( value && *value && dIsspace( *value ) )
value++;
// Trim trailing whitespace
if ( value && *value )
{
char* end = value + 1;
while ( *end && !dIsspace( *end ) )
end++;
*end = '\0';
}
return value;
}
ColladaAppNode::ColladaAppNode(const domNode* node, ColladaAppNode* parent)
: p_domNode(node), appParent(parent), nodeExt(new ColladaExtension_node(node)),
lastTransformTime(TSShapeLoader::DefaultTime-1), defaultTransformValid(false),
invertMeshes(false)
{
mName = dStrdup(_GetNameOrId(node));
mParentName = dStrdup(parent ? parent->getName() : "ROOT");
// Extract user properties from the <node> extension as whitespace separated
// "name=value" pairs
char* properties = dStrdup(nodeExt->user_properties);
char* pos = properties;
char* end = properties + dStrlen( properties );
while ( pos < end )
{
// Find the '=' character to separate the name and value pair
char* split = dStrchr( pos, '=' );
if ( !split )
break;
// Get the name (whitespace trimmed string up to the '=')
// and value (whitespace trimmed string after the '=')
*split = '\0';
char* name = TrimFirstWord( pos );
char* value = TrimFirstWord( split + 1 );
mProps.insert(StringTable->insert(name), dAtof(value));
pos = value + dStrlen( value ) + 1;
}
dFree( properties );
// Create vector of transform elements
for (int iChild = 0; iChild < node->getContents().getCount(); iChild++) {
switch (node->getContents()[iChild]->getElementType()) {
case COLLADA_TYPE::TRANSLATE:
case COLLADA_TYPE::ROTATE:
case COLLADA_TYPE::SCALE:
case COLLADA_TYPE::SKEW:
case COLLADA_TYPE::MATRIX:
case COLLADA_TYPE::LOOKAT:
nodeTransforms.increment();
nodeTransforms.last().element = node->getContents()[iChild];
break;
}
}
}
// Get all child nodes
void ColladaAppNode::buildChildList()
{
// Process children: collect <node> and <instance_node> elements
for (int iChild = 0; iChild < p_domNode->getContents().getCount(); iChild++) {
daeElement* child = p_domNode->getContents()[iChild];
switch (child->getElementType()) {
case COLLADA_TYPE::NODE:
{
domNode* node = daeSafeCast<domNode>(child);
mChildNodes.push_back(new ColladaAppNode(node, this));
break;
}
case COLLADA_TYPE::INSTANCE_NODE:
{
domInstance_node* instanceNode = daeSafeCast<domInstance_node>(child);
domNode* node = daeSafeCast<domNode>(instanceNode->getUrl().getElement());
if (node)
mChildNodes.push_back(new ColladaAppNode(node, this));
else
Con::warnf("Failed to resolve instance_node with url=%s", instanceNode->getUrl().originalStr().c_str());
break;
}
}
}
}
// Get all geometry attached to this node
void ColladaAppNode::buildMeshList()
{
// Process children: collect <instance_geometry> and <instance_controller> elements
for (int iChild = 0; iChild < p_domNode->getContents().getCount(); iChild++) {
daeElement* child = p_domNode->getContents()[iChild];
switch (child->getElementType()) {
case COLLADA_TYPE::INSTANCE_GEOMETRY:
{
// Only <geometry>.<mesh> instances are supported
domInstance_geometry* instanceGeom = daeSafeCast<domInstance_geometry>(child);
if (instanceGeom) {
domGeometry* geometry = daeSafeCast<domGeometry>(instanceGeom->getUrl().getElement());
if (geometry && geometry->getMesh())
mMeshes.push_back(new ColladaAppMesh(instanceGeom, this));
}
break;
}
case COLLADA_TYPE::INSTANCE_CONTROLLER:
mMeshes.push_back(new ColladaAppMesh(daeSafeCast<domInstance_controller>(child), this));
break;
}
}
}
bool ColladaAppNode::animatesTransform(const AppSequence* appSeq)
{
// Check if any of this node's transform elements are animated during the
// sequence interval
for (int iTxfm = 0; iTxfm < nodeTransforms.size(); iTxfm++) {
if (nodeTransforms[iTxfm].isAnimated(appSeq->getStart(), appSeq->getEnd()))
return true;
}
return false;
}
/// Get the world transform of the node at the specified time
MatrixF ColladaAppNode::getNodeTransform(F32 time)
{
// Avoid re-computing the default transform if possible
if (defaultTransformValid && time == TSShapeLoader::DefaultTime)
{
return defaultNodeTransform;
}
else
{
MatrixF nodeTransform = getTransform(time);
// Check for inverted node coordinate spaces => can happen when modelers
// use the 'mirror' tool in their 3d app. Shows up as negative <scale>
// transforms in the collada model.
if (m_matF_determinant(nodeTransform) < 0.0f)
{
// Mark this node as inverted so we can mirror mesh geometry, then
// de-invert the transform matrix
invertMeshes = true;
nodeTransform.scale(Point3F(1, 1, -1));
}
// Cache the default transform
if (time == TSShapeLoader::DefaultTime)
{
defaultTransformValid = true;
defaultNodeTransform = nodeTransform;
}
return nodeTransform;
}
}
MatrixF ColladaAppNode::getTransform(F32 time)
{
// Check if we can use the last computed transform
if (time == lastTransformTime)
return lastTransform;
if (appParent) {
// Get parent node's transform
lastTransform = appParent->getTransform(time);
}
else {
// no parent (ie. root level) => scale by global shape <unit>
lastTransform.identity();
lastTransform.scale(ColladaUtils::getOptions().unit);
if (!isBounds())
ColladaUtils::convertTransform(lastTransform); // don't convert bounds node transform (or upAxis won't work!)
}
// Multiply by local node transform elements
for (int iTxfm = 0; iTxfm < nodeTransforms.size(); iTxfm++) {
MatrixF mat(true);
// Convert the transform element to a MatrixF
switch (nodeTransforms[iTxfm].element->getElementType()) {
case COLLADA_TYPE::TRANSLATE: mat = vecToMatrixF<domTranslate>(nodeTransforms[iTxfm].getValue(time)); break;
case COLLADA_TYPE::SCALE: mat = vecToMatrixF<domScale>(nodeTransforms[iTxfm].getValue(time)); break;
case COLLADA_TYPE::ROTATE: mat = vecToMatrixF<domRotate>(nodeTransforms[iTxfm].getValue(time)); break;
case COLLADA_TYPE::MATRIX: mat = vecToMatrixF<domMatrix>(nodeTransforms[iTxfm].getValue(time)); break;
case COLLADA_TYPE::SKEW: mat = vecToMatrixF<domSkew>(nodeTransforms[iTxfm].getValue(time)); break;
case COLLADA_TYPE::LOOKAT: mat = vecToMatrixF<domLookat>(nodeTransforms[iTxfm].getValue(time)); break;
}
// Remove node scaling (but keep reflections) if desired
if (ColladaUtils::getOptions().ignoreNodeScale)
{
Point3F invScale = mat.getScale();
invScale.x = invScale.x ? (1.0f / invScale.x) : 0;
invScale.y = invScale.y ? (1.0f / invScale.y) : 0;
invScale.z = invScale.z ? (1.0f / invScale.z) : 0;
mat.scale(invScale);
}
// Post multiply the animated transform
lastTransform.mul(mat);
}
lastTransformTime = time;
return lastTransform;
}

View file

@ -0,0 +1,110 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_APPNODE_H_
#define _COLLADA_APPNODE_H_
#ifndef _TDICTIONARY_H_
#include "core/tDictionary.h"
#endif
#ifndef _APPNODE_H_
#include "ts/loader/appNode.h"
#endif
#ifndef _COLLADA_EXTENSIONS_H_
#include "ts/collada/colladaExtensions.h"
#endif
class ColladaAppNode : public AppNode
{
typedef AppNode Parent;
friend class ColladaAppMesh;
MatrixF getTransform(F32 time);
void buildMeshList();
void buildChildList();
protected:
const domNode* p_domNode; ///< Pointer to the node in the Collada DOM
ColladaAppNode* appParent; ///< Parent node in Collada-space
ColladaExtension_node* nodeExt; ///< node extension
Vector<AnimatedFloatList> nodeTransforms; ///< Ordered vector of node transform elements (scale, translate etc)
bool invertMeshes; ///< True if this node's coordinate space is inverted (left handed)
Map<StringTableEntry, F32> mProps; ///< Hash of float properties (converted to int or bool as needed)
F32 lastTransformTime; ///< Time of the last transform lookup (getTransform)
MatrixF lastTransform; ///< Last transform lookup (getTransform)
bool defaultTransformValid; ///< Flag indicating whether the defaultNodeTransform is valid
MatrixF defaultNodeTransform; ///< Transform at DefaultTime
public:
ColladaAppNode(const domNode* node, ColladaAppNode* parent = 0);
virtual ~ColladaAppNode()
{
delete nodeExt;
mProps.clear();
}
const domNode* getDomNode() const { return p_domNode; }
//-----------------------------------------------------------------------
const char *getName() { return mName; }
const char *getParentName() { return mParentName; }
bool isEqual(AppNode* node)
{
const ColladaAppNode* appNode = dynamic_cast<const ColladaAppNode*>(node);
return (appNode && (appNode->p_domNode == p_domNode));
}
// Property look-ups: only float properties are stored, the rest are
// converted from floats as needed
bool getFloat(const char* propName, F32& defaultVal)
{
Map<StringTableEntry,F32>::Iterator itr = mProps.find(propName);
if (itr != mProps.end())
defaultVal = itr->value;
return false;
}
bool getInt(const char* propName, S32& defaultVal)
{
F32 value = defaultVal;
bool ret = getFloat(propName, value);
defaultVal = (S32)value;
return ret;
}
bool getBool(const char* propName, bool& defaultVal)
{
F32 value = defaultVal;
bool ret = getFloat(propName, value);
defaultVal = (value != 0);
return ret;
}
MatrixF getNodeTransform(F32 time);
bool animatesTransform(const AppSequence* appSeq);
bool isParentRoot() { return (appParent == NULL); }
};
#endif // _COLLADA_APPNODE_H_

View file

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "ts/collada/colladaExtensions.h"
#include "ts/collada/colladaAppSequence.h"
ColladaAppSequence::ColladaAppSequence(const domAnimation_clip* clip)
: pClip(clip), clipExt(new ColladaExtension_animation_clip(clip))
{
seqStart = pClip->getStart();
seqEnd = pClip->getEnd();
}
ColladaAppSequence::~ColladaAppSequence()
{
delete clipExt;
}
const char* ColladaAppSequence::getName() const
{
return _GetNameOrId(pClip);
}
S32 ColladaAppSequence::getNumTriggers()
{
return clipExt->triggers.size();
}
void ColladaAppSequence::getTrigger(S32 index, TSShape::Trigger& trigger)
{
trigger.pos = clipExt->triggers[index].time;
trigger.state = clipExt->triggers[index].state;
}
U32 ColladaAppSequence::getFlags() const
{
U32 flags = 0;
if (clipExt->cyclic) flags |= TSShape::Cyclic;
if (clipExt->blend) flags |= TSShape::Blend;
return flags;
}
F32 ColladaAppSequence::getPriority()
{
return clipExt->priority;
}
F32 ColladaAppSequence::getBlendRefTime()
{
return clipExt->blendReferenceTime;
}
void ColladaAppSequence::setActive(bool active)
{
for (int iAnim = 0; iAnim < getClip()->getInstance_animation_array().getCount(); iAnim++) {
domAnimation* anim = daeSafeCast<domAnimation>(getClip()->getInstance_animation_array()[iAnim]->getUrl().getElement());
if (anim)
setAnimationActive(anim, active);
}
}
void ColladaAppSequence::setAnimationActive(const domAnimation* anim, bool active)
{
// Enabled/disable data channels for this animation
for (int iChannel = 0; iChannel < anim->getChannel_array().getCount(); iChannel++) {
domChannel* channel = anim->getChannel_array()[iChannel];
AnimData* animData = reinterpret_cast<AnimData*>(channel->getUserData());
if (animData)
animData->enabled = active;
}
// Recurse into child animations
for (int iAnim = 0; iAnim < anim->getAnimation_array().getCount(); iAnim++)
setAnimationActive(anim->getAnimation_array()[iAnim], active);
}

View file

@ -0,0 +1,65 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_APPSEQUENCE_H_
#define _COLLADA_APPSEQUENCE_H_
#ifndef _APPSEQUENCE_H_
#include "ts/loader/appSequence.h"
#endif
class domAnimation_clip;
class ColladaExtension_animation_clip;
class ColladaAppSequence : public AppSequence
{
const domAnimation_clip* pClip;
ColladaExtension_animation_clip* clipExt;
F32 seqStart;
F32 seqEnd;
void setAnimationActive(const domAnimation* anim, bool active);
public:
ColladaAppSequence(const domAnimation_clip* clip);
~ColladaAppSequence();
void setActive(bool active);
const domAnimation_clip* getClip() const { return pClip; }
S32 getNumTriggers();
void getTrigger(S32 index, TSShape::Trigger& trigger);
const char* getName() const;
F32 getStart() const { return seqStart; }
F32 getEnd() const { return seqEnd; }
void setEnd(F32 end) { seqEnd = end; }
U32 getFlags() const;
F32 getPriority();
F32 getBlendRefTime();
};
#endif // _COLLADA_APPSEQUENCE_H_

View file

@ -0,0 +1,62 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "math/mRandom.h"
#include "ts/collada/colladaExtensions.h"
/// Check if any of the MAYA texture transform elements are animated within
/// the interval
bool ColladaExtension_effect::animatesTextureTransform(F32 start, F32 end)
{
return repeatU.isAnimated(start, end) || repeatV.isAnimated(start, end) ||
offsetU.isAnimated(start, end) || offsetV.isAnimated(start, end) ||
rotateUV.isAnimated(start, end) || noiseU.isAnimated(start, end) ||
noiseV.isAnimated(start, end);
}
/// Apply the MAYA texture transform to the given UV coordinates
void ColladaExtension_effect::applyTextureTransform(Point2F& uv, F32 time)
{
// This function will be called for every tvert, every frame. So cache the
// texture transform parameters to avoid interpolating them every call (since
// they are constant for all tverts for a given 't')
if (time != lastAnimTime) {
// Update texture transform
textureTransform.set(EulerF(0, 0, rotateUV.getValue(time)));
textureTransform.setPosition(Point3F(
offsetU.getValue(time) + noiseU.getValue(time)*gRandGen.randF(),
offsetV.getValue(time) + noiseV.getValue(time)*gRandGen.randF(),
0));
textureTransform.scale(Point3F(repeatU.getValue(time), repeatV.getValue(time), 1.0f));
lastAnimTime = time;
}
// Apply texture transform
Point3F result;
textureTransform.mulP(Point3F(uv.x, uv.y, 0), &result);
uv.x = result.x;
uv.y = result.y;
}

View file

@ -0,0 +1,305 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_EXTENSIONS_H_
#define _COLLADA_EXTENSIONS_H_
#ifndef _TSSHAPE_LOADER_H_
#include "ts/loader/tsShapeLoader.h"
#endif
#ifndef _COLLADA_UTILS_H_
#include "ts/collada/colladaUtils.h"
#endif
//-----------------------------------------------------------------------------
// Collada allows custom data to be included with many elements using the <extra>
// tag, followed by one or more named technique profiles. eg.
// <some_element>
// <extra>
// <technique profile="SOME_PROFILE">
// <custom_element0>value0</custom_element0>
// <custom_element1>value1</custom_element1>
// ...
// <technique profile="ANOTHER_PROFILE">
// <custom_element0>value0</custom_element0>
// <custom_element1>value1</custom_element1>
// ...
//
// This class provides an easy way to read the custom parameters into a strongly
// typed subclass.
class ColladaExtension
{
// Helper macro to simplify getting named parameters
#define GET_EXTRA_PARAM(param, defaultVal) \
get(#param, param, defaultVal)
protected:
const domTechnique* pTechnique;
/// Find the technique with the named profile
template<class T> const domTechnique* findExtraTechnique(const T* element, const char* name) const
{
if (element) {
for (int iExt = 0; iExt < element->getExtra_array().getCount(); iExt++) {
for (int iTech = 0; iTech < element->getExtra_array()[iExt]->getTechnique_array().getCount(); iTech++) {
if (dStrEqual(element->getExtra_array()[iExt]->getTechnique_array()[iTech]->getProfile(), name))
return element->getExtra_array()[iExt]->getTechnique_array()[iTech];
}
}
}
return NULL;
}
/// The <texture> element does not define an extra_array, so need a specialized
/// version of the template
const domTechnique* findExtraTechnique(
const domCommon_color_or_texture_type_complexType::domTexture* element, const char* name) const
{
if (element && element->getExtra()) {
for (int iTech = 0; iTech < element->getExtra()->getTechnique_array().getCount(); iTech++) {
if (dStrEqual(element->getExtra()->getTechnique_array()[iTech]->getProfile(), name))
return element->getExtra()->getTechnique_array()[iTech];
}
}
return NULL;
}
/// Find the parameter with the given name
const domAny* findParam(const char* name)
{
if (pTechnique) {
// search the technique contents for the desired parameter
for (int iParam = 0; iParam < pTechnique->getContents().getCount(); iParam++) {
const domAny* param = daeSafeCast<domAny>(pTechnique->getContents()[iParam]);
if (param && !dStrcmp(param->getElementName(), name))
return param;
}
}
return NULL;
}
/// Get the value of the named parameter (use defaultVal if parameter not found)
template<typename T> void get(const char* name, T& value, T defaultVal)
{
value = defaultVal;
if (const domAny* param = findParam(name))
value = convert<T>(param->getValue());
}
/// Get the value of the named animated parameter (use defaultVal if parameter not found)
template<typename T> void get(const char* name, AnimatedElement<T>& value, T defaultVal)
{
value.defaultVal = defaultVal;
if (const domAny* param = findParam(name))
value.element = param;
}
public:
ColladaExtension() : pTechnique(0) { }
virtual ~ColladaExtension() { }
};
/// Extensions for the <effect> element (and its children)
class ColladaExtension_effect : public ColladaExtension
{
// Cached texture transform
F32 lastAnimTime;
MatrixF textureTransform;
public:
//----------------------------------
// <effect>
// MAX3D profile elements
bool double_sided;
//----------------------------------
// <effect>.<profile_COMMON>
// GOOGLEEARTH profile elements
//bool double_sided;
//----------------------------------
// <effect>.<profile_COMMON>.<technique>.<blinn/phong/lambert>.<diffuse>.<texture>
// MAYA profile elements
bool wrapU, wrapV;
bool mirrorU, mirrorV;
AnimatedFloat coverageU, coverageV;
AnimatedFloat translateFrameU, translateFrameV;
AnimatedFloat rotateFrame;
AnimatedBool stagger; // @todo: not supported yet
AnimatedFloat repeatU, repeatV;
AnimatedFloat offsetU, offsetV;
AnimatedFloat rotateUV;
AnimatedFloat noiseU, noiseV;
//----------------------------------
// <effect>.<profile_COMMON>.<technique>
// FCOLLADA profile elements
domFx_sampler2D_common_complexType* bumpSampler;
public:
ColladaExtension_effect(const domEffect* effect)
: lastAnimTime(TSShapeLoader::DefaultTime-1), textureTransform(true), bumpSampler(0)
{
//----------------------------------
// <effect>
// MAX3D profile
pTechnique = findExtraTechnique(effect, "MAX3D");
GET_EXTRA_PARAM(double_sided, false);
//----------------------------------
// <effect>.<profile_COMMON>
const domProfile_COMMON* profileCommon = ColladaUtils::findEffectCommonProfile(effect);
// GOOGLEEARTH profile (same double_sided element)
pTechnique = findExtraTechnique(profileCommon, "GOOGLEEARTH");
GET_EXTRA_PARAM(double_sided, double_sided);
//----------------------------------
// <effect>.<profile_COMMON>.<technique>.<blinn/phong/lambert>.<diffuse>.<texture>
const domCommon_color_or_texture_type_complexType* domDiffuse = ColladaUtils::findEffectDiffuse(effect);
const domFx_sampler2D_common_complexType* sampler2D = ColladaUtils::getTextureSampler(effect, domDiffuse);
// Use the sampler2D to set default values for wrap/mirror flags
wrapU = wrapV = true;
mirrorU = mirrorV = false;
if (sampler2D) {
domFx_sampler2D_common_complexType::domWrap_s* wrap_s = sampler2D->getWrap_s();
domFx_sampler2D_common_complexType::domWrap_t* wrap_t = sampler2D->getWrap_t();
mirrorU = (wrap_s && wrap_s->getValue() == FX_SAMPLER_WRAP_COMMON_MIRROR);
wrapU = (mirrorU || !wrap_s || (wrap_s->getValue() == FX_SAMPLER_WRAP_COMMON_WRAP));
mirrorV = (wrap_t && wrap_t->getValue() == FX_SAMPLER_WRAP_COMMON_MIRROR);
wrapV = (mirrorV || !wrap_t || (wrap_t->getValue() == FX_SAMPLER_WRAP_COMMON_WRAP));
}
// MAYA profile
pTechnique = findExtraTechnique(domDiffuse ? domDiffuse->getTexture() : 0, "MAYA");
GET_EXTRA_PARAM(wrapU, wrapU); GET_EXTRA_PARAM(wrapV, wrapV);
GET_EXTRA_PARAM(mirrorU, mirrorU); GET_EXTRA_PARAM(mirrorV, mirrorV);
GET_EXTRA_PARAM(coverageU, 1.0); GET_EXTRA_PARAM(coverageV, 1.0);
GET_EXTRA_PARAM(translateFrameU, 0.0); GET_EXTRA_PARAM(translateFrameV, 0.0);
GET_EXTRA_PARAM(rotateFrame, 0.0);
GET_EXTRA_PARAM(stagger, false);
GET_EXTRA_PARAM(repeatU, 1.0); GET_EXTRA_PARAM(repeatV, 1.0);
GET_EXTRA_PARAM(offsetU, 0.0); GET_EXTRA_PARAM(offsetV, 0.0);
GET_EXTRA_PARAM(rotateUV, 0.0);
GET_EXTRA_PARAM(noiseU, 0.0); GET_EXTRA_PARAM(noiseV, 0.0);
// FCOLLADA profile
if (profileCommon) {
pTechnique = findExtraTechnique((const domProfile_COMMON::domTechnique*)profileCommon->getTechnique(), "FCOLLADA");
if (pTechnique) {
domAny* bump = daeSafeCast<domAny>(const_cast<domTechnique*>(pTechnique)->getChild("bump"));
if (bump) {
domAny* bumpTexture = daeSafeCast<domAny>(bump->getChild("texture"));
if (bumpTexture) {
daeSIDResolver resolver(const_cast<domEffect*>(effect), bumpTexture->getAttribute("texture").c_str());
domCommon_newparam_type* param = daeSafeCast<domCommon_newparam_type>(resolver.getElement());
if (param)
bumpSampler = param->getSampler2D();
}
}
}
}
}
/// Check if any of the MAYA texture transform elements are animated within
/// the interval
bool animatesTextureTransform(F32 start, F32 end);
/// Apply the MAYA texture transform to the given UV coordinates
void applyTextureTransform(Point2F& uv, F32 time);
};
/// Extensions for the <node> element
class ColladaExtension_node : public ColladaExtension
{
public:
// FCOLLADA or OpenCOLLADA profile elements
AnimatedFloat visibility;
const char* user_properties;
ColladaExtension_node(const domNode* node)
{
// FCOLLADA profile
pTechnique = findExtraTechnique(node, "FCOLLADA");
GET_EXTRA_PARAM(visibility, 1.0);
GET_EXTRA_PARAM(user_properties, "");
// OpenCOLLADA profile
pTechnique = findExtraTechnique(node, "OpenCOLLADA");
if (!visibility.element)
GET_EXTRA_PARAM(visibility, 1.0);
GET_EXTRA_PARAM(user_properties, user_properties);
}
};
/// Extensions for the <geometry> element
class ColladaExtension_geometry : public ColladaExtension
{
public:
// MAYA profile elements
bool double_sided;
ColladaExtension_geometry(const domGeometry* geometry)
{
// MAYA profile
pTechnique = findExtraTechnique(geometry, "MAYA");
GET_EXTRA_PARAM(double_sided, false);
}
};
// Extensions for the <animation_clip> element
class ColladaExtension_animation_clip : public ColladaExtension
{
public:
struct Trigger {
F32 time;
S32 state;
};
// Torque profile elements (none of these are animatable)
S32 num_triggers;
Vector<Trigger> triggers;
bool cyclic;
bool blend;
F32 blendReferenceTime;
F32 priority;
ColladaExtension_animation_clip(const domAnimation_clip* clip)
{
// Torque profile
pTechnique = findExtraTechnique(clip, "Torque");
GET_EXTRA_PARAM(num_triggers, 0);
for (int iTrigger = 0; iTrigger < num_triggers; iTrigger++) {
triggers.increment();
get(avar("trigger_time%d", iTrigger), triggers.last().time, 0.0f);
get(avar("trigger_state%d", iTrigger), triggers.last().state, 0);
}
GET_EXTRA_PARAM(cyclic, false);
GET_EXTRA_PARAM(blend, false);
GET_EXTRA_PARAM(blendReferenceTime, 0.0f);
GET_EXTRA_PARAM(priority, 5.0f);
}
};
#endif // _COLLADA_EXTENSIONS_H_

View file

@ -0,0 +1,259 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/volume.h"
#include "ts/collada/colladaUtils.h"
#include "ts/collada/colladaAppNode.h"
#include "ts/collada/colladaShapeLoader.h"
#include "gui/controls/guiTreeViewCtrl.h"
// Helper struct for counting nodes, meshes and polygons down through the scene
// hierarchy
struct SceneStats
{
S32 numNodes;
S32 numMeshes;
S32 numPolygons;
S32 numMaterials;
S32 numLights;
S32 numClips;
SceneStats() : numNodes(0), numMeshes(0), numPolygons(0), numMaterials(0), numLights(0), numClips(0) { }
};
// Recurse through the <visual_scene> adding nodes and geometry to the GuiTreeView control
static void processNode(GuiTreeViewCtrl* tree, domNode* node, S32 parentID, SceneStats& stats)
{
stats.numNodes++;
S32 nodeID = tree->insertItem(parentID, _GetNameOrId(node), "node", "", 0, 0);
// Update mesh and poly counts
for (int i = 0; i < node->getContents().getCount(); i++)
{
domGeometry* geom = 0;
const char* elemName = "";
daeElement* child = node->getContents()[i];
switch (child->getElementType())
{
case COLLADA_TYPE::INSTANCE_GEOMETRY:
{
domInstance_geometry* instgeom = daeSafeCast<domInstance_geometry>(child);
if (instgeom)
{
geom = daeSafeCast<domGeometry>(instgeom->getUrl().getElement());
elemName = _GetNameOrId(geom);
}
break;
}
case COLLADA_TYPE::INSTANCE_CONTROLLER:
{
domInstance_controller* instctrl = daeSafeCast<domInstance_controller>(child);
if (instctrl)
{
domController* ctrl = daeSafeCast<domController>(instctrl->getUrl().getElement());
elemName = _GetNameOrId(ctrl);
if (ctrl && ctrl->getSkin())
geom = daeSafeCast<domGeometry>(ctrl->getSkin()->getSource().getElement());
else if (ctrl && ctrl->getMorph())
geom = daeSafeCast<domGeometry>(ctrl->getMorph()->getSource().getElement());
}
break;
}
case COLLADA_TYPE::INSTANCE_LIGHT:
stats.numLights++;
tree->insertItem(nodeID, _GetNameOrId(node), "light", "", 0, 0);
break;
}
if (geom && geom->getMesh())
{
const char* name = _GetNameOrId(node);
if ( dStrEqual( name, "null" ) || dStrEndsWith( name, "PIVOT" ) )
name = _GetNameOrId( daeSafeCast<domNode>(node->getParent()) );
stats.numMeshes++;
tree->insertItem(nodeID, name, "mesh", "", 0, 0);
for (S32 j = 0; j < geom->getMesh()->getTriangles_array().getCount(); j++)
stats.numPolygons += geom->getMesh()->getTriangles_array()[j]->getCount();
for (S32 j = 0; j < geom->getMesh()->getTristrips_array().getCount(); j++)
stats.numPolygons += geom->getMesh()->getTristrips_array()[j]->getCount();
for (S32 j = 0; j < geom->getMesh()->getTrifans_array().getCount(); j++)
stats.numPolygons += geom->getMesh()->getTrifans_array()[j]->getCount();
for (S32 j = 0; j < geom->getMesh()->getPolygons_array().getCount(); j++)
stats.numPolygons += geom->getMesh()->getPolygons_array()[j]->getCount();
for (S32 j = 0; j < geom->getMesh()->getPolylist_array().getCount(); j++)
stats.numPolygons += geom->getMesh()->getPolylist_array()[j]->getCount();
}
}
// Recurse into child nodes
for (S32 i = 0; i < node->getNode_array().getCount(); i++)
processNode(tree, node->getNode_array()[i], nodeID, stats);
for (S32 i = 0; i < node->getInstance_node_array().getCount(); i++)
{
domInstance_node* instnode = node->getInstance_node_array()[i];
domNode* node = daeSafeCast<domNode>(instnode->getUrl().getElement());
if (node)
processNode(tree, node, nodeID, stats);
}
}
ConsoleFunction( enumColladaForImport, bool, 3, 3,
"(string shapePath, GuiTreeViewCtrl ctrl) Collect scene information from "
"a COLLADA file and store it in a GuiTreeView control. This function is "
"used by the COLLADA import gui to show a preview of the scene contents "
"prior to import, and is probably not much use for anything else.\n"
"@param shapePath COLLADA filename\n"
"@param ctrl GuiTreeView control to add elements to\n"
"@return true if successful, false otherwise\n"
"@ingroup Editors\n"
"@internal")
{
GuiTreeViewCtrl* tree;
if (!Sim::findObject(argv[2], tree))
{
Con::errorf("enumColladaScene::Could not find GuiTreeViewCtrl '%s'", argv[2]);
return false;
}
// Check if a cached DTS is available => no need to import the collada file
// if we can load the DTS instead
Torque::Path path(argv[1]);
if (ColladaShapeLoader::canLoadCachedDTS(path))
return false;
// Check if this is a Sketchup file (.kmz) and if so, mount the zip filesystem
// and get the path to the DAE file.
String mountPoint;
Torque::Path daePath;
bool isSketchup = ColladaShapeLoader::checkAndMountSketchup(path, mountPoint, daePath);
// Load the Collada file into memory
domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(daePath);
if (!root)
{
TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete");
return false;
}
if (isSketchup)
{
// Unmount the zip if we mounted it
Torque::FS::Unmount(mountPoint);
}
// Initialize tree
tree->removeItem(0);
S32 nodesID = tree->insertItem(0, "Shape", "", "", 0, 0);
S32 matsID = tree->insertItem(0, "Materials", "", "", 0, 0);
S32 animsID = tree->insertItem(0, "Animations", "", "", 0, 0);
SceneStats stats;
// Query DOM for shape summary details
for (int i = 0; i < root->getLibrary_visual_scenes_array().getCount(); i++)
{
const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[i];
for (int j = 0; j < libScenes->getVisual_scene_array().getCount(); j++)
{
const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[j];
for (int k = 0; k < visualScene->getNode_array().getCount(); k++)
processNode(tree, visualScene->getNode_array()[k], nodesID, stats);
}
}
// Get material count
for (S32 i = 0; i < root->getLibrary_materials_array().getCount(); i++)
{
const domLibrary_materials* libraryMats = root->getLibrary_materials_array()[i];
stats.numMaterials += libraryMats->getMaterial_array().getCount();
for (S32 j = 0; j < libraryMats->getMaterial_array().getCount(); j++)
{
domMaterial* mat = libraryMats->getMaterial_array()[j];
tree->insertItem(matsID, _GetNameOrId(mat), _GetNameOrId(mat), "", 0, 0);
}
}
// Get animation count
for (S32 i = 0; i < root->getLibrary_animation_clips_array().getCount(); i++)
{
const domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[i];
stats.numClips += libraryClips->getAnimation_clip_array().getCount();
for (S32 j = 0; j < libraryClips->getAnimation_clip_array().getCount(); j++)
{
domAnimation_clip* clip = libraryClips->getAnimation_clip_array()[j];
tree->insertItem(animsID, _GetNameOrId(clip), "animation", "", 0, 0);
}
}
if (stats.numClips == 0)
{
// No clips => check if there are any animations (these will be added to a default clip)
for (S32 i = 0; i < root->getLibrary_animations_array().getCount(); i++)
{
const domLibrary_animations* libraryAnims = root->getLibrary_animations_array()[i];
if (libraryAnims->getAnimation_array().getCount())
{
stats.numClips = 1;
tree->insertItem(animsID, "ambient", "animation", "", 0, 0);
break;
}
}
}
// Extract the global scale and up_axis from the top level <asset> element,
F32 unit = 1.0f;
domUpAxisType upAxis = UPAXISTYPE_Z_UP;
if (root->getAsset()) {
if (root->getAsset()->getUnit())
unit = root->getAsset()->getUnit()->getMeter();
if (root->getAsset()->getUp_axis())
upAxis = root->getAsset()->getUp_axis()->getValue();
}
TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete");
// Store shape information in the tree control
tree->setDataField(StringTable->insert("_nodeCount"), 0, avar("%d", stats.numNodes));
tree->setDataField(StringTable->insert("_meshCount"), 0, avar("%d", stats.numMeshes));
tree->setDataField(StringTable->insert("_polygonCount"), 0, avar("%d", stats.numPolygons));
tree->setDataField(StringTable->insert("_materialCount"), 0, avar("%d", stats.numMaterials));
tree->setDataField(StringTable->insert("_lightCount"), 0, avar("%d", stats.numLights));
tree->setDataField(StringTable->insert("_animCount"), 0, avar("%d", stats.numClips));
tree->setDataField(StringTable->insert("_unit"), 0, avar("%g", unit));
if (upAxis == UPAXISTYPE_X_UP)
tree->setDataField(StringTable->insert("_upAxis"), 0, "X_AXIS");
else if (upAxis == UPAXISTYPE_Y_UP)
tree->setDataField(StringTable->insert("_upAxis"), 0, "Y_AXIS");
else
tree->setDataField(StringTable->insert("_upAxis"), 0, "Z_AXIS");
return true;
}

View file

@ -0,0 +1,236 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "ts/collada/colladaUtils.h"
#include "ts/collada/colladaAppNode.h"
#include "ts/collada/colladaShapeLoader.h"
#include "T3D/pointLight.h"
#include "T3D/spotLight.h"
//-----------------------------------------------------------------------------
// Collada <light> elements are very similar, but are arranged as separate, unrelated
// classes. These template functions are used to provide a simple way to access the
// common elements.
template<class T> static void resolveLightColor(T* light, ColorF& color)
{
if (light->getColor())
{
color.red = light->getColor()->getValue()[0];
color.green = light->getColor()->getValue()[1];
color.blue = light->getColor()->getValue()[2];
}
}
template<class T> static void resolveLightAttenuation(T* light, Point3F& attenuationRatio)
{
if (light->getConstant_attenuation())
attenuationRatio.x = light->getConstant_attenuation()->getValue();
if (light->getLinear_attenuation())
attenuationRatio.y = light->getLinear_attenuation()->getValue();
if (light->getQuadratic_attenuation())
attenuationRatio.z = light->getQuadratic_attenuation()->getValue();
}
//-----------------------------------------------------------------------------
// Recurse through the collada scene to add <light>s to the Torque scene
static void processNodeLights(AppNode* appNode, const MatrixF& offset, SimGroup* group)
{
const domNode* node = dynamic_cast<ColladaAppNode*>(appNode)->getDomNode();
for (S32 iLight = 0; iLight < node->getInstance_light_array().getCount(); iLight++) {
domInstance_light* instLight = node->getInstance_light_array()[iLight];
domLight* p_domLight = daeSafeCast<domLight>(instLight->getUrl().getElement());
if (!p_domLight) {
Con::warnf("Failed to find light for URL \"%s\"", instLight->getUrl().getOriginalURI());
continue;
}
String lightName = Sim::getUniqueName(_GetNameOrId(node));
const char* lightType = "";
domLight::domTechnique_common* technique = p_domLight->getTechnique_common();
if (!technique) {
Con::warnf("No <technique_common> for light \"%s\"", lightName.c_str());
continue;
}
LightBase* pLight = 0;
ColorF color(ColorF::WHITE);
Point3F attenuation(0, 1, 1);
if (technique->getAmbient()) {
domLight::domTechnique_common::domAmbient* ambient = technique->getAmbient();
// No explicit support for ambient lights, so use a PointLight instead
lightType = "ambient";
pLight = new PointLight;
resolveLightColor(ambient, color);
}
else if (technique->getDirectional()) {
domLight::domTechnique_common::domDirectional* directional = technique->getDirectional();
// No explicit support for directional lights, so use a SpotLight instead
lightType = "directional";
pLight = new SpotLight;
resolveLightColor(directional, color);
}
else if (technique->getPoint()) {
domLight::domTechnique_common::domPoint* point = technique->getPoint();
lightType = "point";
pLight = new PointLight;
resolveLightColor(point, color);
resolveLightAttenuation(point, attenuation);
}
else if (technique->getSpot()) {
domLight::domTechnique_common::domSpot* spot = technique->getSpot();
lightType = "spot";
pLight = new SpotLight;
resolveLightColor(spot, color);
resolveLightAttenuation(spot, attenuation);
}
else
continue;
Con::printf("Adding <%s> light \"%s\" as a %s", lightType, lightName.c_str(), pLight->getClassName());
MatrixF mat(offset);
mat.mul(appNode->getNodeTransform(TSShapeLoader::DefaultTime));
pLight->setDataField(StringTable->insert("color"), 0,
avar("%f %f %f %f", color.red, color.green, color.blue, color.alpha));
pLight->setDataField(StringTable->insert("attenuationRatio"), 0,
avar("%f %f %f", attenuation.x, attenuation.y, attenuation.z));
pLight->setTransform(mat);
if (!pLight->registerObject(lightName)) {
Con::errorf(ConsoleLogEntry::General, "Failed to register light for \"%s\"", lightName.c_str());
delete pLight;
}
if (group)
group->addObject(pLight);
}
// Recurse child nodes
for (S32 iChild = 0; iChild < appNode->getNumChildNodes(); iChild++)
processNodeLights(appNode->getChildNode(iChild), offset, group);
}
// Load lights from a collada file and add to the scene.
ConsoleFunction( loadColladaLights, bool, 2, 4,
"(string filename, SimGroup parentGroup=MissionGroup, SimObject baseObject=-1)"
"Load all light instances from a COLLADA (.dae) file and add to the scene.\n"
"@param filename COLLADA filename to load lights from\n"
"@param parentGroup (optional) name of an existing simgroup to add the new "
"lights to (defaults to MissionGroup)\n"
"@param baseObject (optional) name of an object to use as the origin (useful "
"if you are loading the lights for a collada scene and have moved or rotated "
"the geometry)\n"
"@return true if successful, false otherwise\n\n"
"@tsexample\n"
"// load the lights in room.dae\n"
"loadColladaLights( \"art/shapes/collada/room.dae\" );\n\n"
"// load the lights in room.dae and add them to the RoomLights group\n"
"loadColladaLights( \"art/shapes/collada/room.dae\", \"RoomLights\" );\n\n"
"// load the lights in room.dae and use the transform of the \"Room\"\n"
"object as the origin\n"
"loadColladaLights( \"art/shapes/collada/room.dae\", \"\", \"Room\" );\n"
"@endtsexample\n\n"
"@note Currently for editor use only\n"
"@ingroup Editors\n"
"@internal")
{
Torque::Path path(argv[1]);
// Optional group to add the lights to. Create if it does not exist, and use
// the MissionGroup if not specified.
SimGroup* missionGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup"));
SimGroup* group = 0;
if ((argc > 2) && (argv[2][0])) {
if (!Sim::findObject(argv[2], group)) {
// Create the group if it could not be found
group = new SimGroup;
if (group->registerObject(argv[2])) {
if (missionGroup)
missionGroup->addObject(group);
}
else {
delete group;
group = 0;
}
}
}
if (!group)
group = missionGroup;
// Optional object to provide the base transform
MatrixF offset(true);
if (argc > 3) {
SceneObject *obj;
if (Sim::findObject(argv[3], obj))
offset = obj->getTransform();
}
// Load the Collada file into memory
domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(path);
if (!root) {
TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete");
return false;
}
// Extract the global scale and up_axis from the top level <asset> element,
F32 unit = 1.0f;
domUpAxisType upAxis = UPAXISTYPE_Z_UP;
if (root->getAsset()) {
if (root->getAsset()->getUnit())
unit = root->getAsset()->getUnit()->getMeter();
if (root->getAsset()->getUp_axis())
upAxis = root->getAsset()->getUp_axis()->getValue();
}
ColladaUtils::getOptions().unit = unit;
ColladaUtils::getOptions().upAxis = upAxis;
// First grab all of the top-level nodes
Vector<ColladaAppNode*> sceneNodes;
for (int iSceneLib = 0; iSceneLib < root->getLibrary_visual_scenes_array().getCount(); iSceneLib++) {
const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[iSceneLib];
for (int iScene = 0; iScene < libScenes->getVisual_scene_array().getCount(); iScene++) {
const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[iScene];
for (int iNode = 0; iNode < visualScene->getNode_array().getCount(); iNode++)
sceneNodes.push_back(new ColladaAppNode(visualScene->getNode_array()[iNode]));
}
}
// Recurse the scene tree looking for <instance_light>s
for (S32 iNode = 0; iNode < sceneNodes.size(); iNode++) {
processNodeLights(sceneNodes[iNode], offset, group);
delete sceneNodes[iNode];
}
TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete");
return true;
}

View file

@ -0,0 +1,725 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
/*
Resource stream -> Buffer
Buffer -> Collada DOM
Collada DOM -> TSShapeLoader
TSShapeLoader installed into TSShape
*/
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "ts/collada/colladaShapeLoader.h"
#include "ts/collada/colladaUtils.h"
#include "ts/collada/colladaAppNode.h"
#include "ts/collada/colladaAppMesh.h"
#include "ts/collada/colladaAppMaterial.h"
#include "ts/collada/colladaAppSequence.h"
#include "core/util/tVector.h"
#include "core/strings/findMatch.h"
#include "core/stream/fileStream.h"
#include "core/fileObject.h"
#include "ts/tsShape.h"
#include "ts/tsShapeInstance.h"
#include "materials/materialManager.h"
#include "console/persistenceManager.h"
#include "ts/tsShapeConstruct.h"
#include "core/util/zip/zipVolume.h"
#include "gfx/bitmap/gBitmap.h"
//
static DAE sDAE; // Collada model database (holds the last loaded file)
static Torque::Path sLastPath; // Path of the last loaded Collada file
static FileTime sLastModTime; // Modification time of the last loaded Collada file
//-----------------------------------------------------------------------------
// Custom warning/error message handler
class myErrorHandler : public daeErrorHandler
{
void handleError( daeString msg )
{
Con::errorf("Error: %s", msg);
}
void handleWarning( daeString msg )
{
Con::errorf("Warning: %s", msg);
}
} sErrorHandler;
//-----------------------------------------------------------------------------
ColladaShapeLoader::ColladaShapeLoader(domCOLLADA* _root)
: root(_root)
{
// Extract the global scale and up_axis from the top level <asset> element,
F32 unit = 1.0f;
domUpAxisType upAxis = UPAXISTYPE_Z_UP;
if (root->getAsset()) {
if (root->getAsset()->getUnit())
unit = root->getAsset()->getUnit()->getMeter();
if (root->getAsset()->getUp_axis())
upAxis = root->getAsset()->getUp_axis()->getValue();
}
// Set import options (if they are not set to override)
if (ColladaUtils::getOptions().unit <= 0.0f)
ColladaUtils::getOptions().unit = unit;
if (ColladaUtils::getOptions().upAxis == UPAXISTYPE_COUNT)
ColladaUtils::getOptions().upAxis = upAxis;
}
ColladaShapeLoader::~ColladaShapeLoader()
{
// Delete all of the animation channels
for (int iAnim = 0; iAnim < animations.size(); iAnim++) {
for (int iChannel = 0; iChannel < animations[iAnim]->size(); iChannel++)
delete (*animations[iAnim])[iChannel];
delete animations[iAnim];
}
animations.clear();
}
void ColladaShapeLoader::processAnimation(const domAnimation* anim, F32& maxEndTime, F32& minFrameTime)
{
const char* sRGBANames[] = { ".R", ".G", ".B", ".A", "" };
const char* sXYZNames[] = { ".X", ".Y", ".Z", "" };
const char* sXYZANames[] = { ".X", ".Y", ".Z", ".ANGLE" };
const char* sLOOKATNames[] = { ".POSITIONX", ".POSITIONY", ".POSITIONZ", ".TARGETX", ".TARGETY", ".TARGETZ", ".UPX", ".UPY", ".UPZ", "" };
const char* sSKEWNames[] = { ".ROTATEX", ".ROTATEY", ".ROTATEZ", ".AROUNDX", ".AROUNDY", ".AROUNDZ", ".ANGLE", "" };
const char* sNullNames[] = { "" };
for (int iChannel = 0; iChannel < anim->getChannel_array().getCount(); iChannel++) {
// Get the animation elements: <channel>, <sampler>
domChannel* channel = anim->getChannel_array()[iChannel];
domSampler* sampler = daeSafeCast<domSampler>(channel->getSource().getElement());
if (!sampler)
continue;
// Find the animation channel target
daeSIDResolver resolver(channel, channel->getTarget());
daeElement* target = resolver.getElement();
if (!target) {
daeErrorHandler::get()->handleWarning(avar("Failed to resolve animation "
"target: %s", channel->getTarget()));
continue;
}
/*
// If the target is a <source>, point it at the array instead
// @todo:Only support targeting float arrays for now...
if (target->getElementType() == COLLADA_TYPE::SOURCE)
{
domSource* source = daeSafeCast<domSource>(target);
if (source->getFloat_array())
target = source->getFloat_array();
}
*/
// Get the target's animation channels (create them if not already)
if (!AnimData::getAnimChannels(target)) {
animations.push_back(new AnimChannels(target));
}
AnimChannels* targetChannels = AnimData::getAnimChannels(target);
// Add a new animation channel to the target
targetChannels->push_back(new AnimData());
channel->setUserData(targetChannels->last());
AnimData& data = *targetChannels->last();
for (int iInput = 0; iInput < sampler->getInput_array().getCount(); iInput++) {
const domInputLocal* input = sampler->getInput_array()[iInput];
const domSource* source = daeSafeCast<domSource>(input->getSource().getElement());
if (!source)
continue;
// @todo:don't care about the input param names for now. Could
// validate against the target type....
if (dStrEqual(input->getSemantic(), "INPUT")) {
data.input.initFromSource(source);
// Adjust the maximum sequence end time
maxEndTime = getMax(maxEndTime, data.input.getFloatValue((S32)data.input.size()-1));
// Detect the frame rate (minimum time between keyframes)
for (S32 iFrame = 1; iFrame < data.input.size(); iFrame++)
{
F32 delta = data.input.getFloatValue( iFrame ) - data.input.getFloatValue( iFrame-1 );
if ( delta < 0 )
{
daeErrorHandler::get()->handleError(avar("<animation> INPUT '%s' "
"has non-monotonic keys. Animation is unlikely to be imported correctly.", source->getID()));
break;
}
minFrameTime = getMin( minFrameTime, delta );
}
}
else if (dStrEqual(input->getSemantic(), "OUTPUT"))
data.output.initFromSource(source);
else if (dStrEqual(input->getSemantic(), "IN_TANGENT"))
data.inTangent.initFromSource(source);
else if (dStrEqual(input->getSemantic(), "OUT_TANGENT"))
data.outTangent.initFromSource(source);
else if (dStrEqual(input->getSemantic(), "INTERPOLATION"))
data.interpolation.initFromSource(source);
}
// Set initial value for visibility targets that were added automatically (in colladaUtils.cpp
if (dStrEqual(target->getElementName(), "visibility"))
{
domAny* visTarget = daeSafeCast<domAny>(target);
if (visTarget && dStrEqual(visTarget->getValue(), ""))
visTarget->setValue(avar("%g", data.output.getFloatValue(0)));
}
// Ignore empty animations
if (data.input.size() == 0) {
channel->setUserData(0);
delete targetChannels->last();
targetChannels->pop_back();
continue;
}
// Determine the number and offset the elements of the target value
// targeted by this animation
switch (target->getElementType()) {
case COLLADA_TYPE::COLOR: data.parseTargetString(channel->getTarget(), 4, sRGBANames); break;
case COLLADA_TYPE::TRANSLATE: data.parseTargetString(channel->getTarget(), 3, sXYZNames); break;
case COLLADA_TYPE::ROTATE: data.parseTargetString(channel->getTarget(), 4, sXYZANames); break;
case COLLADA_TYPE::SCALE: data.parseTargetString(channel->getTarget(), 3, sXYZNames); break;
case COLLADA_TYPE::LOOKAT: data.parseTargetString(channel->getTarget(), 3, sLOOKATNames); break;
case COLLADA_TYPE::SKEW: data.parseTargetString(channel->getTarget(), 3, sSKEWNames); break;
case COLLADA_TYPE::MATRIX: data.parseTargetString(channel->getTarget(), 16, sNullNames); break;
case COLLADA_TYPE::FLOAT_ARRAY: data.parseTargetString(channel->getTarget(), daeSafeCast<domFloat_array>(target)->getCount(), sNullNames); break;
default: data.parseTargetString(channel->getTarget(), 1, sNullNames); break;
}
}
// Process child animations
for (int iAnim = 0; iAnim < anim->getAnimation_array().getCount(); iAnim++)
processAnimation(anim->getAnimation_array()[iAnim], maxEndTime, minFrameTime);
}
void ColladaShapeLoader::enumerateScene()
{
// Get animation clips
Vector<const domAnimation_clip*> animationClips;
for (int iClipLib = 0; iClipLib < root->getLibrary_animation_clips_array().getCount(); iClipLib++) {
const domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[iClipLib];
for (int iClip = 0; iClip < libraryClips->getAnimation_clip_array().getCount(); iClip++)
appSequences.push_back(new ColladaAppSequence(libraryClips->getAnimation_clip_array()[iClip]));
}
// Process all animations => this attaches animation channels to the targeted
// Collada elements, and determines the length of the sequence if it is not
// already specified in the Collada <animation_clip> element
for (int iSeq = 0; iSeq < appSequences.size(); iSeq++) {
ColladaAppSequence* appSeq = dynamic_cast<ColladaAppSequence*>(appSequences[iSeq]);
F32 maxEndTime = 0;
F32 minFrameTime = 1000.0f;
for (int iAnim = 0; iAnim < appSeq->getClip()->getInstance_animation_array().getCount(); iAnim++) {
domAnimation* anim = daeSafeCast<domAnimation>(appSeq->getClip()->getInstance_animation_array()[iAnim]->getUrl().getElement());
if (anim)
processAnimation(anim, maxEndTime, minFrameTime);
}
if (appSeq->getEnd() == 0)
appSeq->setEnd(maxEndTime);
// Collada animations can be stored as sampled frames or true keyframes. For
// sampled frames, use the same frame rate as the DAE file. For true keyframes,
// resample at a fixed frame rate.
appSeq->fps = mClamp(1.0f / minFrameTime + 0.5f, TSShapeLoader::MinFrameRate, TSShapeLoader::MaxFrameRate);
}
// First grab all of the top-level nodes
Vector<domNode*> sceneNodes;
for (int iSceneLib = 0; iSceneLib < root->getLibrary_visual_scenes_array().getCount(); iSceneLib++) {
const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[iSceneLib];
for (int iScene = 0; iScene < libScenes->getVisual_scene_array().getCount(); iScene++) {
const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[iScene];
for (int iNode = 0; iNode < visualScene->getNode_array().getCount(); iNode++)
sceneNodes.push_back(visualScene->getNode_array()[iNode]);
}
}
// Set LOD option
bool singleDetail = true;
switch (ColladaUtils::getOptions().lodType)
{
case ColladaUtils::ImportOptions::DetectDTS:
// Check for a baseXX->startXX hierarchy at the top-level, if we find
// one, use trailing numbers for LOD, otherwise use a single size
for (int iNode = 0; singleDetail && (iNode < sceneNodes.size()); iNode++) {
domNode* node = sceneNodes[iNode];
if (dStrStartsWith(_GetNameOrId(node), "base")) {
for (int iChild = 0; iChild < node->getNode_array().getCount(); iChild++) {
domNode* child = node->getNode_array()[iChild];
if (dStrStartsWith(_GetNameOrId(child), "start")) {
singleDetail = false;
break;
}
}
}
}
break;
case ColladaUtils::ImportOptions::SingleSize:
singleDetail = true;
break;
case ColladaUtils::ImportOptions::TrailingNumber:
singleDetail = false;
break;
default:
break;
}
ColladaAppMesh::fixDetailSize( singleDetail, ColladaUtils::getOptions().singleDetailSize );
// Process the top level nodes
for (S32 iNode = 0; iNode < sceneNodes.size(); iNode++) {
ColladaAppNode* node = new ColladaAppNode(sceneNodes[iNode], 0);
if (!processNode(node))
delete node;
}
// Make sure that the scene has a bounds node (for getting the root scene transform)
if (!boundsNode)
{
domVisual_scene* visualScene = root->getLibrary_visual_scenes_array()[0]->getVisual_scene_array()[0];
domNode* dombounds = daeSafeCast<domNode>( visualScene->createAndPlace( "node" ) );
dombounds->setName( "bounds" );
ColladaAppNode *appBounds = new ColladaAppNode(dombounds, 0);
if (!processNode(appBounds))
delete appBounds;
}
}
bool ColladaShapeLoader::ignoreNode(const String& name)
{
if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImport, name, false))
return false;
else
return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImport, name, false);
}
bool ColladaShapeLoader::ignoreMesh(const String& name)
{
if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImportMesh, name, false))
return false;
else
return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMesh, name, false);
}
void ColladaShapeLoader::computeBounds(Box3F& bounds)
{
TSShapeLoader::computeBounds(bounds);
// Check if the model origin needs adjusting
if ( bounds.isValidBox() &&
(ColladaUtils::getOptions().adjustCenter ||
ColladaUtils::getOptions().adjustFloor) )
{
// Compute shape offset
Point3F shapeOffset = Point3F::Zero;
if ( ColladaUtils::getOptions().adjustCenter )
{
bounds.getCenter( &shapeOffset );
shapeOffset = -shapeOffset;
}
if ( ColladaUtils::getOptions().adjustFloor )
shapeOffset.z = -bounds.minExtents.z;
// Adjust bounds
bounds.minExtents += shapeOffset;
bounds.maxExtents += shapeOffset;
// Now adjust all positions for root level nodes (nodes with no parent)
for (S32 iNode = 0; iNode < shape->nodes.size(); iNode++)
{
if ( !appNodes[iNode]->isParentRoot() )
continue;
// Adjust default translation
shape->defaultTranslations[iNode] += shapeOffset;
// Adjust animated translations
for (S32 iSeq = 0; iSeq < shape->sequences.size(); iSeq++)
{
const TSShape::Sequence& seq = shape->sequences[iSeq];
if ( seq.translationMatters.test(iNode) )
{
for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
{
S32 index = seq.baseTranslation + seq.translationMatters.count(iNode)*seq.numKeyframes + iFrame;
shape->nodeTranslations[index] += shapeOffset;
}
}
}
}
}
}
//-----------------------------------------------------------------------------
/// Find the file extension for an extensionless texture
String findTextureExtension(const Torque::Path &texPath)
{
Torque::Path path(texPath);
for(S32 i = 0;i < GBitmap::sRegistrations.size();++i)
{
GBitmap::Registration &reg = GBitmap::sRegistrations[i];
for(S32 j = 0;j < reg.extensions.size();++j)
{
path.setExtension(reg.extensions[j]);
if (Torque::FS::IsFile(path))
return path.getExtension();
}
}
return String();
}
//-----------------------------------------------------------------------------
/// Copy a texture from a KMZ to a cache. Note that the texture filename is modified
void copySketchupTexture(const Torque::Path &path, String &textureFilename)
{
if (textureFilename.isEmpty())
return;
Torque::Path texturePath(textureFilename);
texturePath.setExtension(findTextureExtension(texturePath));
String cachedTexFilename = String::ToString("%s_%s.cached",
TSShapeLoader::getShapePath().getFileName().c_str(), texturePath.getFileName().c_str());
Torque::Path cachedTexPath;
cachedTexPath.setRoot(path.getRoot());
cachedTexPath.setPath(path.getPath());
cachedTexPath.setFileName(cachedTexFilename);
cachedTexPath.setExtension(texturePath.getExtension());
FileStream *source;
FileStream *dest;
if ((source = FileStream::createAndOpen(texturePath.getFullPath(), Torque::FS::File::Read)) == NULL)
return;
if ((dest = FileStream::createAndOpen(cachedTexPath.getFullPath(), Torque::FS::File::Write)) == NULL)
{
delete source;
return;
}
dest->copyFrom(source);
delete dest;
delete source;
// Update the filename in the material
cachedTexPath.setExtension("");
textureFilename = cachedTexPath.getFullPath();
}
//-----------------------------------------------------------------------------
/// Add collada materials to materials.cs
void updateMaterialsScript(const Torque::Path &path, bool copyTextures = false)
{
#ifdef DAE2DTS_TOOL
if (!ColladaUtils::getOptions().forceUpdateMaterials)
return;
#endif
Torque::Path scriptPath(path);
scriptPath.setFileName("materials");
scriptPath.setExtension("cs");
// First see what materials we need to update
PersistenceManager persistMgr;
for ( U32 iMat = 0; iMat < AppMesh::appMaterials.size(); iMat++ )
{
ColladaAppMaterial *mat = dynamic_cast<ColladaAppMaterial*>( AppMesh::appMaterials[iMat] );
if ( mat )
{
Material *mappedMat;
if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) )
{
// Only update existing materials if forced to
if ( ColladaUtils::getOptions().forceUpdateMaterials )
persistMgr.setDirty( mappedMat );
}
else
{
// Create a new material definition
persistMgr.setDirty( mat->createMaterial( scriptPath ), scriptPath.getFullPath() );
}
}
}
if ( persistMgr.getDirtyList().empty() )
return;
// If importing a sketchup file, the paths will point inside the KMZ so we need to cache them.
if (copyTextures)
{
for (int iMat = 0; iMat < persistMgr.getDirtyList().size(); iMat++)
{
Material *mat = dynamic_cast<Material*>( persistMgr.getDirtyList()[iMat].getObject() );
copySketchupTexture(path, mat->mDiffuseMapFilename[0]);
copySketchupTexture(path, mat->mNormalMapFilename[0]);
copySketchupTexture(path, mat->mSpecularMapFilename[0]);
}
}
persistMgr.saveDirty();
}
//-----------------------------------------------------------------------------
/// Check if an up-to-date cached DTS is available for this DAE file
bool ColladaShapeLoader::canLoadCachedDTS(const Torque::Path& path)
{
// Generate the cached filename
Torque::Path cachedPath(path);
cachedPath.setExtension("cached.dts");
// Check if a cached DTS newer than this file is available
FileTime cachedModifyTime;
if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime))
{
bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false);
FileTime daeModifyTime;
if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) ||
(!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) ))
{
// DAE not found, or cached DTS is newer
return true;
}
}
return false;
}
bool ColladaShapeLoader::checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath)
{
bool isSketchup = path.getExtension().equal("kmz", String::NoCase);
if (isSketchup)
{
// Mount the zip so files can be found (it will be unmounted before we return)
mountPoint = String("sketchup_") + path.getFileName();
String zipPath = path.getFullPath();
if (!Torque::FS::Mount(mountPoint, new Torque::ZipFileSystem(zipPath)))
return false;
Vector<String> daeFiles;
Torque::Path findPath;
findPath.setRoot(mountPoint);
S32 results = Torque::FS::FindByPattern(findPath, "*.dae", true, daeFiles);
if (results == 0 || daeFiles.size() == 0)
{
Torque::FS::Unmount(mountPoint);
return false;
}
daePath = daeFiles[0];
}
else
{
daePath = path;
}
return isSketchup;
}
//-----------------------------------------------------------------------------
/// Get the root collada DOM element for the given DAE file
domCOLLADA* ColladaShapeLoader::getDomCOLLADA(const Torque::Path& path)
{
daeErrorHandler::setErrorHandler(&sErrorHandler);
TSShapeLoader::updateProgress(TSShapeLoader::Load_ReadFile, path.getFullFileName().c_str());
// Check if we can use the last loaded file
FileTime daeModifyTime;
if (Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime))
{
if ((path == sLastPath) && (Platform::compareFileTimes(sLastModTime, daeModifyTime) >= 0))
return sDAE.getRoot(path.getFullPath().c_str());
}
sDAE.clear();
sDAE.setBaseURI("");
TSShapeLoader::updateProgress(TSShapeLoader::Load_ParseFile, "Parsing XML...");
domCOLLADA* root = readColladaFile(path.getFullPath());
if (!root)
{
TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed");
sDAE.clear();
return NULL;
}
sLastPath = path;
sLastModTime = daeModifyTime;
return root;
}
domCOLLADA* ColladaShapeLoader::readColladaFile(const String& path)
{
// Check if this file is already loaded into the database
domCOLLADA* root = sDAE.getRoot(path.c_str());
if (root)
return root;
// Load the Collada file into memory
FileObject fo;
if (!fo.readMemory(path))
{
daeErrorHandler::get()->handleError(avar("Could not read %s into memory", path.c_str()));
return NULL;
}
root = sDAE.openFromMemory(path.c_str(), (const char*)fo.buffer());
if (!root || !root->getLibrary_visual_scenes_array().getCount()) {
daeErrorHandler::get()->handleError(avar("Could not parse %s", path.c_str()));
return NULL;
}
// Fixup issues in the model
ColladaUtils::applyConditioners(root);
// Recursively load external DAE references
TSShapeLoader::updateProgress(TSShapeLoader::Load_ExternalRefs, "Loading external references...");
for (S32 iRef = 0; iRef < root->getDocument()->getReferencedDocuments().getCount(); iRef++) {
String refPath = (daeString)root->getDocument()->getReferencedDocuments()[iRef];
if (refPath.endsWith(".dae") && !readColladaFile(refPath))
daeErrorHandler::get()->handleError(avar("Failed to load external reference: %s", refPath.c_str()));
}
return root;
}
//-----------------------------------------------------------------------------
/// This function is invoked by the resource manager based on file extension.
TSShape* loadColladaShape(const Torque::Path &path)
{
#ifndef DAE2DTS_TOOL
// Generate the cached filename
Torque::Path cachedPath(path);
cachedPath.setExtension("cached.dts");
// Check if an up-to-date cached DTS version of this file exists, and
// if so, use that instead.
if (ColladaShapeLoader::canLoadCachedDTS(path))
{
FileStream cachedStream;
cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read);
if (cachedStream.getStatus() == Stream::Ok)
{
TSShape *shape = new TSShape;
bool readSuccess = shape->read(&cachedStream);
cachedStream.close();
if (readSuccess)
{
#ifdef TORQUE_DEBUG
Con::printf("Loaded cached Collada shape from %s", cachedPath.getFullPath().c_str());
#endif
return shape;
}
else
delete shape;
}
Con::warnf("Failed to load cached COLLADA shape from %s", cachedPath.getFullPath().c_str());
}
#endif // DAE2DTS_TOOL
if (!Torque::FS::IsFile(path))
{
// DAE file does not exist, bail.
return NULL;
}
#ifdef DAE2DTS_TOOL
ColladaUtils::ImportOptions cmdLineOptions = ColladaUtils::getOptions();
#endif
// Allow TSShapeConstructor object to override properties
ColladaUtils::getOptions().reset();
TSShapeConstructor* tscon = TSShapeConstructor::findShapeConstructor(path.getFullPath());
if (tscon)
{
ColladaUtils::getOptions() = tscon->mOptions;
#ifdef DAE2DTS_TOOL
// Command line overrides certain options
ColladaUtils::getOptions().forceUpdateMaterials = cmdLineOptions.forceUpdateMaterials;
ColladaUtils::getOptions().useDiffuseNames = cmdLineOptions.useDiffuseNames;
#endif
}
// Check if this is a Sketchup file (.kmz) and if so, mount the zip filesystem
// and get the path to the DAE file.
String mountPoint;
Torque::Path daePath;
bool isSketchup = ColladaShapeLoader::checkAndMountSketchup(path, mountPoint, daePath);
// Load Collada model and convert to 3space
TSShape* tss = 0;
domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(daePath);
if (root)
{
ColladaShapeLoader loader(root);
tss = loader.generateShape(daePath);
if (tss)
{
#ifndef DAE2DTS_TOOL
// Cache the Collada model to a DTS file for faster loading next time.
FileStream dtsStream;
if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write))
{
Con::printf("Writing cached COLLADA shape to %s", cachedPath.getFullPath().c_str());
tss->write(&dtsStream);
}
#endif // DAE2DTS_TOOL
// Add collada materials to materials.cs
updateMaterialsScript(path, isSketchup);
}
}
// Close progress dialog
TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete");
if (isSketchup)
{
// Unmount the zip if we mounted it
Torque::FS::Unmount(mountPoint);
}
return tss;
}

View file

@ -0,0 +1,61 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_SHAPELOADER_H_
#define _COLLADA_SHAPELOADER_H_
#ifndef _TSSHAPELOADER_H_
#include "ts/loader/tsShapeLoader.h"
#endif
class domCOLLADA;
class domAnimation;
struct AnimChannels;
//-----------------------------------------------------------------------------
class ColladaShapeLoader : public TSShapeLoader
{
friend TSShape* loadColladaShape(const Torque::Path &path);
domCOLLADA* root;
Vector<AnimChannels*> animations; ///< Holds all animation channels for deletion after loading
void processAnimation(const domAnimation* anim, F32& maxEndTime, F32& minFrameTime);
void cleanup();
public:
ColladaShapeLoader(domCOLLADA* _root);
~ColladaShapeLoader();
void enumerateScene();
bool ignoreNode(const String& name);
bool ignoreMesh(const String& name);
void computeBounds(Box3F& bounds);
static bool canLoadCachedDTS(const Torque::Path& path);
static bool checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath);
static domCOLLADA* getDomCOLLADA(const Torque::Path& path);
static domCOLLADA* readColladaFile(const String& path);
};
#endif // _COLLADA_SHAPELOADER_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,810 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _COLLADA_UTILS_H_
#define _COLLADA_UTILS_H_
#ifdef _MSC_VER
#pragma warning(disable : 4786) // disable warning about long debug symbol names
#pragma warning(disable : 4355) // disable "'this' : used in base member initializer list" warnings
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
#ifndef _MQUAT_H_
#include "math/mQuat.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TSSHAPE_LOADER_H_
#include "ts/loader/tsShapeLoader.h"
#endif
#ifndef _OPTIMIZEDPOLYLIST_H_
#include "collision/optimizedPolyList.h"
#endif
#ifndef TINYXML_INCLUDED
#include "tinyxml.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
#include "platform/tmm_off.h"
#include "dae.h"
#include "dae/daeErrorHandler.h"
#include "dae/domAny.h"
#include "dom/domProfile_COMMON.h"
#include "dom/domMaterial.h"
#include "dom/domGeometry.h"
#include "dom/domMorph.h"
#include "dom/domNode.h"
#include "dom/domCOLLADA.h"
#include "platform/tmm_on.h"
namespace ColladaUtils
{
struct ImportOptions
{
enum eLodType
{
DetectDTS = 0,
SingleSize,
TrailingNumber,
NumLodTypes
};
domUpAxisType upAxis; // Override for the collada <up_axis> element
F32 unit; // Override for the collada <unit> element
eLodType lodType; // LOD type option
S32 singleDetailSize; // Detail size for all meshes in the model
String matNamePrefix; // Prefix to apply to collada material names
String alwaysImport; // List of node names (with wildcards) to import, even if in the neverImport list
String neverImport; // List of node names (with wildcards) to ignore on loading
String alwaysImportMesh; // List of mesh names (with wildcards) to import, even if in the neverImportMesh list
String neverImportMesh; // List of mesh names (with wildcards) to ignore on loading
bool ignoreNodeScale; // Ignore <scale> elements in <node>s
bool adjustCenter; // Translate model so origin is at the center
bool adjustFloor; // Translate model so origin is at the bottom
bool forceUpdateMaterials; // Force update of materials.cs
bool useDiffuseNames; // Use diffuse texture as the material name
ImportOptions()
{
reset();
}
void reset()
{
upAxis = UPAXISTYPE_COUNT;
unit = -1.0f;
lodType = DetectDTS;
singleDetailSize = 2;
matNamePrefix = "";
alwaysImport = "";
neverImport = "";
alwaysImportMesh = "";
neverImportMesh = "";
ignoreNodeScale = false;
adjustCenter = false;
adjustFloor = false;
forceUpdateMaterials = false;
useDiffuseNames = false;
}
};
ImportOptions& getOptions();
void convertTransform(MatrixF& m);
void collapsePath(std::string& path);
// Apply the set of Collada conditioners (suited for loading Collada models into Torque)
void applyConditioners(domCOLLADA* root);
const domProfile_COMMON* findEffectCommonProfile(const domEffect* effect);
const domCommon_color_or_texture_type_complexType* findEffectDiffuse(const domEffect* effect);
const domCommon_color_or_texture_type_complexType* findEffectSpecular(const domEffect* effect);
const domFx_sampler2D_common_complexType* getTextureSampler(const domEffect* effect, const domCommon_color_or_texture_type_complexType* texture);
String getSamplerImagePath(const domEffect* effect, const domFx_sampler2D_common_complexType* sampler2D);
String resolveImagePath(const domImage* image);
// Collada export helper functions
Torque::Path findTexture(const Torque::Path& diffuseMap);
void exportColladaHeader(TiXmlElement* rootNode);
void exportColladaMaterials(TiXmlElement* rootNode, const OptimizedPolyList& mesh, Vector<String>& matNames, const Torque::Path& colladaFile);
void exportColladaTriangles(TiXmlElement* meshNode, const OptimizedPolyList& mesh, const String& meshName, const Vector<String>& matNames);
void exportColladaMesh(TiXmlElement* rootNode, const OptimizedPolyList& mesh, const String& meshName, const Vector<String>& matNames);
void exportColladaScene(TiXmlElement* rootNode, const String& meshName, const Vector<String>& matNames);
// Export an OptimizedPolyList to a simple Collada file
void exportToCollada(const Torque::Path& colladaFile, const OptimizedPolyList& mesh, const String& meshName = String::EmptyString);
};
//-----------------------------------------------------------------------------
// Helper Classes
//
// The Collada DOM uses a different class for each XML element, and there is very
// little class inheritance, even though many elements have the same attributes
// and children. This makes the DOM a bit ugly to work with, and the following
// templates attempt to make this situation a bit nicer by providing a common way
// to access common elements, while retaining the strong typing of the DOM classes.
//-----------------------------------------------------------------------------
/// Convert from the Collada transform types to a Torque MatrixF
template<class T> inline MatrixF vecToMatrixF(const domListOfFloats& vec) { return MatrixF(true); }
/// Collada <translate>: [x_translate, y_translate, z_translate]
template<> inline MatrixF vecToMatrixF<domTranslate>(const domListOfFloats& vec)
{
MatrixF mat(true);
mat.setPosition(Point3F(vec[0], vec[1], vec[2]));
return mat;
}
/// Collada <scale>: [x_scale, y_scale, z_scale]
template<> inline MatrixF vecToMatrixF<domScale>(const domListOfFloats& vec)
{
MatrixF mat(true);
mat.scale(Point3F(vec[0], vec[1], vec[2]));
return mat;
}
/// Collada <rotate>: [rotation_axis, angle_in_degrees]
template<> inline MatrixF vecToMatrixF<domRotate>(const domListOfFloats& vec)
{
AngAxisF aaxis(Point3F(vec[0], vec[1], vec[2]), -(vec[3] * M_PI) / 180.0f);
MatrixF mat(true);
aaxis.setMatrix(&mat);
return mat;
}
/// Collada <matrix>: same form as TGE (woohoo!)
template<> inline MatrixF vecToMatrixF<domMatrix>(const domListOfFloats& vec)
{
MatrixF mat;
for (int i = 0; i < 16; i++)
mat[i] = vec[i];
return mat;
}
/// Collada <skew>: [angle_in_degrees, rotation_axis, translation_axis]
/// skew transform code adapted from GMANMatrix4 implementation
template<> inline MatrixF vecToMatrixF<domSkew>(const domListOfFloats& vec)
{
F32 angle = -(vec[0] * M_PI) / 180.0f;
Point3F rotAxis(vec[1], vec[2], vec[3]);
Point3F transAxis(vec[4], vec[5], vec[6]);
transAxis.normalize();
Point3F a1 = transAxis * mDot(rotAxis, transAxis);
Point3F a2 = rotAxis - a1;
a2.normalize();
F32 an1 = mDot(rotAxis, a2);
F32 an2 = mDot(rotAxis, transAxis);
F32 rx = an1 * mCos(angle) - an2 * mSin(angle);
F32 ry = an1 * mSin(angle) + an2 * mCos(angle);
// Check for rotation parallel to translation
F32 alpha = (an1 == 0) ? 0 : (ry/rx - an2/an1);
MatrixF mat(true);
mat(0,0) = a2.x * transAxis.x * alpha + 1.0;
mat(1,0) = a2.y * transAxis.x * alpha;
mat(2,0) = a2.z * transAxis.x * alpha;
mat(0,1) = a2.x * transAxis.y * alpha;
mat(1,1) = a2.y * transAxis.y * alpha + 1.0;
mat(2,1) = a2.z * transAxis.y * alpha;
mat(0,2) = a2.x * transAxis.z * alpha;
mat(1,2) = a2.y * transAxis.z * alpha;
mat(2,2) = a2.z * transAxis.z * alpha + 1.0;
return mat;
}
/// Collada <lookat>: [eye, target, up]
template<> inline MatrixF vecToMatrixF<domLookat>(const domListOfFloats& vec)
{
Point3F eye(vec[0], vec[1], vec[2]);
Point3F target(vec[3], vec[4], vec[5]);
Point3F up(vec[6], vec[7], vec[8]);
Point3F fwd = target - eye;
fwd.normalizeSafe();
Point3F right = mCross(fwd, up);
right.normalizeSafe();
up = mCross(right, fwd);
up.normalizeSafe();
MatrixF mat(true);
mat.setColumn(0, right);
mat.setColumn(1, fwd);
mat.setColumn(2, up);
mat.setColumn(3, eye);
return mat;
}
//-----------------------------------------------------------------------------
/// Try to get a name for the element using the following attributes (in order):
/// name, sid, id, "null"
template<class T> inline const char* _GetNameOrId(const T* element)
{
return element ? (element->getName() ? element->getName() : (element->getId() ? element->getId() : "null")) : "null";
}
template<> inline const char* _GetNameOrId(const domInstance_geometry* element)
{
return element ? (element->getName() ? element->getName() : (element->getSid() ? element->getSid() : "null")) : "null";
}
template<> inline const char* _GetNameOrId(const domInstance_controller* element)
{
return element ? (element->getName() ? element->getName() : (element->getSid() ? element->getSid() : "null")) : "null";
}
//-----------------------------------------------------------------------------
// Collada <source>s are extremely flexible, and thus difficult to access in a nice
// way. This class attempts to provide a clean interface to convert Collada source
// data to the appropriate Torque data structure without losing any of the flexibility
// of the underlying Collada DOM.
//
// Some of the conversions we need to handle are:
// - daeString to const char*
// - daeIDRef to const char*
// - double to F32
// - double to Point2F
// - double to Point3F
// - double to MatrixF
//
// The _SourceReader object is initialized with a list of parameter names that it
// tries to match to <param> elements in the source accessor to figure out how to
// pull values out of the 1D source array. Note that no type checking of any kind
// is done until we actually try to extract values from the source.
class _SourceReader
{
const domSource* source; // the wrapped Collada source
const domAccessor* accessor; // shortcut to the source accessor
Vector<U32> offsets; // offset of each of the desired values to pull from the source array
public:
_SourceReader() : source(0), accessor(0) {}
void reset()
{
source = 0;
accessor = 0;
offsets.clear();
}
//------------------------------------------------------
// Initialize the _SourceReader object
bool initFromSource(const domSource* src, const char* paramNames[] = 0)
{
source = src;
accessor = source->getTechnique_common()->getAccessor();
offsets.clear();
// The source array has groups of values in a 1D stream => need to map the
// input param names to source params to determine the offset within the
// group for each desired value
U32 paramCount = 0;
while (paramNames && paramNames[paramCount][0]) {
// lookup the index of the source param that matches the input param
offsets.push_back(paramCount);
for (U32 iParam = 0; iParam < accessor->getParam_array().getCount(); iParam++) {
if (accessor->getParam_array()[iParam]->getName() &&
dStrEqual(accessor->getParam_array()[iParam]->getName(), paramNames[paramCount])) {
offsets.last() = iParam;
break;
}
}
paramCount++;
}
// If no input params were specified, just map the source params directly
if (!offsets.size()) {
for (int iParam = 0; iParam < accessor->getParam_array().getCount(); iParam++)
offsets.push_back(iParam);
}
return true;
}
//------------------------------------------------------
// Shortcut to the size of the array (should be the number of destination objects)
S32 size() const { return accessor ? accessor->getCount() : 0; }
// Get the number of elements per group in the source
S32 stride() const { return accessor ? accessor->getStride() : 0; }
//------------------------------------------------------
// Get a pointer to the start of a group of values (index advances by stride)
//template<class T> T getArrayData(int index) const { return 0; }
const double* getStringArrayData(int index) const
{
if ((index >= 0) && (index < size())) {
if (source->getFloat_array())
return &source->getFloat_array()->getValue()[index*stride()];
}
return 0;
}
//------------------------------------------------------
// Read a single value from the source array
//template<class T> T getValue(int index) const { return T; }
const char* getStringValue(int index) const
{
if ((index >= 0) && (index < size())) {
// could be plain strings or IDREFs
if (source->getName_array())
return source->getName_array()->getValue()[index*stride()];
else if (source->getIDREF_array())
return source->getIDREF_array()->getValue()[index*stride()].getID();
}
return "";
}
F32 getFloatValue(int index) const
{
F32 value(0);
if (const double* data = getStringArrayData(index))
return data[offsets[0]];
return value;
}
Point2F getPoint2FValue(int index) const
{
Point2F value(0, 0);
if (const double* data = getStringArrayData(index))
value.set(data[offsets[0]], data[offsets[1]]);
return value;
}
Point3F getPoint3FValue(int index) const
{
Point3F value(1, 0, 0);
if (const double* data = getStringArrayData(index))
value.set(data[offsets[0]], data[offsets[1]], data[offsets[2]]);
return value;
}
ColorI getColorIValue(int index) const
{
ColorI value(255, 255, 255, 255);
if (const double* data = getStringArrayData(index))
{
value.red = data[offsets[0]] * 255.0;
value.green = data[offsets[1]] * 255.0;
value.blue = data[offsets[2]] * 255.0;
if ( stride() == 4 )
value.alpha = data[offsets[3]] * 255.0;
}
return value;
}
MatrixF getMatrixFValue(int index) const
{
MatrixF value(true);
if (const double* data = getStringArrayData(index)) {
for (int i = 0; i < 16; i++)
value[i] = data[i];
}
return value;
}
};
//-----------------------------------------------------------------------------
// Collada geometric primitives: Use the BasePrimitive class to access the
// different primitive types in a nice way.
class BasePrimitive
{
public:
/// Return true if the element is a geometric primitive type
static bool isPrimitive(const daeElement* element)
{
switch (element->getElementType()) {
case COLLADA_TYPE::TRIANGLES: case COLLADA_TYPE::POLYLIST:
case COLLADA_TYPE::POLYGONS: case COLLADA_TYPE::TRIFANS:
case COLLADA_TYPE::TRISTRIPS: case COLLADA_TYPE::CAPSULE:
case COLLADA_TYPE::CYLINDER: case COLLADA_TYPE::LINES:
case COLLADA_TYPE::LINESTRIPS: case COLLADA_TYPE::PLANE:
case COLLADA_TYPE::SPLINE: case COLLADA_TYPE::SPHERE:
case COLLADA_TYPE::TAPERED_CAPSULE: case COLLADA_TYPE::TAPERED_CYLINDER:
return true;
}
return false;
}
/// Return true if the element is a supported primitive type
static bool isSupportedPrimitive(const daeElement* element)
{
switch (element->getElementType()) {
case COLLADA_TYPE::TRIANGLES:
case COLLADA_TYPE::TRISTRIPS:
case COLLADA_TYPE::TRIFANS:
case COLLADA_TYPE::POLYLIST:
case COLLADA_TYPE::POLYGONS:
return true;
}
return false;
}
/// Construct a child class based on the type of Collada element
static BasePrimitive* get(const daeElement* element);
/// Methods to be implemented for each supported Collada geometric element
virtual const char* getElementName() = 0;
virtual const char* getMaterial() = 0;
virtual const domInputLocalOffset_Array& getInputs() = 0;
virtual S32 getStride() const = 0;
virtual const domListOfUInts *getTriangleData() = 0;
};
/// Template child class for supported Collada primitive elements
template<class T> class ColladaPrimitive : public BasePrimitive
{
T* primitive;
domListOfUInts *pTriangleData;
S32 stride;
public:
ColladaPrimitive(const daeElement* e) : pTriangleData(0)
{
// Cast to geometric primitive element
primitive = daeSafeCast<T>(const_cast<daeElement*>(e));
// Determine stride
stride = 0;
for (int iInput = 0; iInput < getInputs().getCount(); iInput++) {
if (getInputs()[iInput]->getOffset() >= stride)
stride = getInputs()[iInput]->getOffset() + 1;
}
}
~ColladaPrimitive()
{
delete pTriangleData;
}
/// Most primitives can use these common implementations
const char* getElementName() { return primitive->getElementName(); }
const char* getMaterial() { return primitive->getMaterial(); }
const domInputLocalOffset_Array& getInputs() { return primitive->getInput_array(); }
S32 getStride() const { return stride; }
/// Each supported primitive needs to implement this method (and convert
/// to triangles if required)
const domListOfUInts *getTriangleData() { return NULL; }
};
//-----------------------------------------------------------------------------
// <triangles>
template<> inline const domListOfUInts *ColladaPrimitive<domTriangles>::getTriangleData()
{
// Return the <p> integer list directly
return (primitive->getP() ? &(primitive->getP()->getValue()) : NULL);
}
//-----------------------------------------------------------------------------
// <tristrips>
template<> inline const domListOfUInts *ColladaPrimitive<domTristrips>::getTriangleData()
{
if (!pTriangleData)
{
// Convert strips to triangles
pTriangleData = new domListOfUInts();
for (int iStrip = 0; iStrip < primitive->getCount(); iStrip++) {
domP* P = primitive->getP_array()[iStrip];
// Ignore invalid P arrays
if (!P || !P->getValue().getCount())
continue;
domUint* pSrcData = &(P->getValue()[0]);
S32 numTriangles = (P->getValue().getCount() / stride) - 2;
// Convert the strip back to a triangle list
domUint* v0 = pSrcData;
for (int iTri = 0; iTri < numTriangles; iTri++, v0 += stride) {
if (iTri & 0x1)
{
// CW triangle
pTriangleData->appendArray(stride, v0);
pTriangleData->appendArray(stride, v0 + 2*stride);
pTriangleData->appendArray(stride, v0 + stride);
}
else
{
// CCW triangle
pTriangleData->appendArray(stride*3, v0);
}
}
}
}
return pTriangleData;
}
//-----------------------------------------------------------------------------
// <trifans>
template<> inline const domListOfUInts *ColladaPrimitive<domTrifans>::getTriangleData()
{
if (!pTriangleData)
{
// Convert strips to triangles
pTriangleData = new domListOfUInts();
for (int iStrip = 0; iStrip < primitive->getCount(); iStrip++) {
domP* P = primitive->getP_array()[iStrip];
// Ignore invalid P arrays
if (!P || !P->getValue().getCount())
continue;
domUint* pSrcData = &(P->getValue()[0]);
S32 numTriangles = (P->getValue().getCount() / stride) - 2;
// Convert the fan back to a triangle list
domUint* v0 = pSrcData + stride;
for (int iTri = 0; iTri < numTriangles; iTri++, v0 += stride) {
pTriangleData->appendArray(stride, pSrcData); // shared vertex
pTriangleData->appendArray(stride, v0); // previous vertex
pTriangleData->appendArray(stride, v0+stride); // current vertex
}
}
}
return pTriangleData;
}
//-----------------------------------------------------------------------------
// <polygons>
template<> inline const domListOfUInts *ColladaPrimitive<domPolygons>::getTriangleData()
{
if (!pTriangleData)
{
// Convert polygons to triangles
pTriangleData = new domListOfUInts();
for (int iPoly = 0; iPoly < primitive->getCount(); iPoly++) {
domP* P = primitive->getP_array()[iPoly];
// Ignore invalid P arrays
if (!P || !P->getValue().getCount())
continue;
domUint* pSrcData = &(P->getValue()[0]);
S32 numPoints = P->getValue().getCount() / stride;
// Use a simple tri-fan (centered at the first point) method of
// converting the polygon to triangles.
domUint* v0 = pSrcData;
pSrcData += stride;
for (int iTri = 0; iTri < numPoints-2; iTri++) {
pTriangleData->appendArray(stride, v0);
pTriangleData->appendArray(stride*2, pSrcData);
pSrcData += stride;
}
}
}
return pTriangleData;
}
//-----------------------------------------------------------------------------
// <polylist>
template<> inline const domListOfUInts *ColladaPrimitive<domPolylist>::getTriangleData()
{
if (!pTriangleData)
{
// Convert polygons to triangles
pTriangleData = new domListOfUInts();
// Check that the P element has the right number of values (this
// has been seen with certain models exported using COLLADAMax)
const domListOfUInts& vcount = primitive->getVcount()->getValue();
U32 expectedCount = 0;
for (int iPoly = 0; iPoly < vcount.getCount(); iPoly++)
expectedCount += vcount[iPoly];
expectedCount *= stride;
if (!primitive->getP() || !primitive->getP()->getValue().getCount() ||
(primitive->getP()->getValue().getCount() != expectedCount) )
{
Con::warnf("<polylist> element found with invalid <p> array. This primitive will be ignored.");
return pTriangleData;
}
domUint* pSrcData = &(primitive->getP()->getValue()[0]);
for (int iPoly = 0; iPoly < vcount.getCount(); iPoly++) {
// Use a simple tri-fan (centered at the first point) method of
// converting the polygon to triangles.
domUint* v0 = pSrcData;
pSrcData += stride;
for (int iTri = 0; iTri < vcount[iPoly]-2; iTri++) {
pTriangleData->appendArray(stride, v0);
pTriangleData->appendArray(stride*2, pSrcData);
pSrcData += stride;
}
pSrcData += stride;
}
}
return pTriangleData;
}
//-----------------------------------------------------------------------------
/// Convert a custom parameter string to a particular type
template<typename T> inline T convert(const char* value) { return value; }
template<> inline bool convert(const char* value) { return dAtob(value); }
template<> inline S32 convert(const char* value) { return dAtoi(value); }
template<> inline double convert(const char* value) { return dAtof(value); }
template<> inline F32 convert(const char* value) { return convert<double>(value); }
//-----------------------------------------------------------------------------
/// Collada animation data
struct AnimChannels : public Vector<struct AnimData*>
{
daeElement *element;
AnimChannels(daeElement* el) : element(el)
{
element->setUserData(this);
}
~AnimChannels()
{
if (element)
element->setUserData(0);
}
};
struct AnimData
{
bool enabled; ///!< Used to select animation channels for the current clip
_SourceReader input;
_SourceReader output;
_SourceReader inTangent;
_SourceReader outTangent;
_SourceReader interpolation;
U32 targetValueOffset; ///< Offset into the target element (for arrays of values)
U32 targetValueCount; ///< Number of values animated (from OUTPUT source array)
/// Get the animation channels for the Collada element (if any)
static AnimChannels* getAnimChannels(const daeElement* element)
{
return element ? (AnimChannels*)const_cast<daeElement*>(element)->getUserData() : 0;
}
AnimData() : enabled(false) { }
void parseTargetString(const char* target, int fullCount, const char* elements[]);
F32 invertParamCubic(F32 param, F32 x0, F32 x1, F32 x2, F32 x3) const;
void interpValue(F32 t, U32 offset, double* value) const;
void interpValue(F32 t, U32 offset, const char** value) const;
};
//-----------------------------------------------------------------------------
// Collada allows any element with an SID or ID attribute to be the target of
// an animation channel, which is very flexible, but awkward to work with. Some
// examples of animated values are:
// - single float
// - single int
// - single bool
// - single string
// - list of floats (transform elements or morph weights)
//
// This class provides a generic way to check if an element is animated, and
// to get the value of the element at a given time.
template<class T>
struct AnimatedElement
{
const daeElement* element; ///< The Collada element (can be NULL)
T defaultVal; ///< Default value (used when element is NULL)
AnimatedElement(const daeElement* e=0) : element(e) { }
/// Check if the element has any animations channels
bool isAnimated() { return (AnimData::getAnimChannels(element) != 0); }
bool isAnimated(F32 start, F32 end) { return isAnimated(); }
/// Get the value of the element at the specified time
T getValue(F32 time)
{
// If the element is NULL, just use the default (handy for <extra> profiles which
// may or may not be present in the document)
T value(defaultVal);
if (const domAny* param = daeSafeCast<domAny>(const_cast<daeElement*>(element))) {
// If the element is not animated, just use its current value
value = convert<T>(param->getValue());
// Animate the value
const AnimChannels* channels = AnimData::getAnimChannels(element);
if (channels && (time >= 0)) {
for (int iChannel = 0; iChannel < channels->size(); iChannel++) {
const AnimData* animData = (*channels)[iChannel];
if (animData->enabled)
animData->interpValue(time, 0, &value);
}
}
}
return value;
}
};
template<class T> struct AnimatedElementList : public AnimatedElement<T>
{
AnimatedElementList(const daeElement* e=0) : AnimatedElement<T>(e) { }
// @todo: Disable morph animations for now since they are not supported by T3D
bool isAnimated() { return false; }
bool isAnimated(F32 start, F32 end) { return false; }
// Get the value of the element list at the specified time
T getValue(F32 time)
{
T vec(this->defaultVal);
if (this->element) {
// Get a copy of the vector
vec = *(T*)const_cast<daeElement*>(this->element)->getValuePointer();
// Animate the vector
const AnimChannels* channels = AnimData::getAnimChannels(this->element);
if (channels && (time >= 0)) {
for (int iChannel = 0; iChannel < channels->size(); iChannel++) {
const AnimData* animData = (*channels)[iChannel];
if (animData->enabled) {
for (int iValue = 0; iValue < animData->targetValueCount; iValue++)
animData->interpValue(time, iValue, &vec[animData->targetValueOffset + iValue]);
}
}
}
}
return vec;
}
};
// Strongly typed animated values
typedef AnimatedElement<double> AnimatedFloat;
typedef AnimatedElement<bool> AnimatedBool;
typedef AnimatedElement<S32> AnimatedInt;
typedef AnimatedElement<const char*> AnimatedString;
typedef AnimatedElementList<domListOfFloats> AnimatedFloatList;
#endif // _COLLADA_UTILS_H_