Merge pull request #47 from OTHGMars/Assimp_Mats

Material Update.
This commit is contained in:
Areloch 2019-04-27 11:26:52 -05:00 committed by GitHub
commit a1097fbd13
16 changed files with 807 additions and 134 deletions

View file

@ -405,8 +405,10 @@ inline void Buffer::Read(Value& obj, Asset& r)
inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset)
{
byteLength = length ? length : stream.FileSize();
//T3D_CHANGE_BEGIN
if ((byteLength + baseOffset) > stream.FileSize())
byteLength = stream.FileSize() - baseOffset;
//T3D_CHANGE_END
if (baseOffset) {
stream.Seek(baseOffset, aiOrigin_SET);

View file

@ -822,8 +822,6 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector<unsigned int>&
if (node.skin) {
for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) {
aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]+primitiveNo];
mesh->mNumBones = static_cast<unsigned int>(node.skin->jointNames.size());
mesh->mBones = new aiBone*[mesh->mNumBones];
// GLTF and Assimp choose to store bone weights differently.
// GLTF has each vertex specify which bones influence the vertex.
@ -834,39 +832,98 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector<unsigned int>&
// both because it's somewhat slow and because, for many applications,
// we then need to reconvert the data back into the vertex-to-bone
// mapping which makes things doubly-slow.
std::vector<std::vector<aiVertexWeight>> weighting(mesh->mNumBones);
//T3D_CHANGE_BEGIN
// The following commented block has been completely replaced.
// Portions of the replacement code block have been taken from:
// https://github.com/ConfettiFX/The-Forge/blob/master/Common_3/ThirdParty/OpenSource/assimp/4.1.0/code/glTF2Importer.cpp#L823-L860
//std::vector<std::vector<aiVertexWeight>> weighting(mesh->mNumBones);
//BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting);
//for (uint32_t i = 0; i < mesh->mNumBones; ++i) {
// aiBone* bone = new aiBone();
// Ref<Node> joint = node.skin->jointNames[i];
// if (!joint->name.empty()) {
// bone->mName = joint->name;
// } else {
// // Assimp expects each bone to have a unique name.
// static const std::string kDefaultName = "bone_";
// char postfix[10] = {0};
// ASSIMP_itoa10(postfix, i);
// bone->mName = (kDefaultName + postfix);
// }
// GetNodeTransform(bone->mOffsetMatrix, *joint);
// std::vector<aiVertexWeight>& weights = weighting[i];
// bone->mNumWeights = static_cast<uint32_t>(weights.size());
// if (bone->mNumWeights > 0) {
// bone->mWeights = new aiVertexWeight[bone->mNumWeights];
// memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
// } else {
// // Assimp expects all bones to have at least 1 weight.
// bone->mWeights = new aiVertexWeight[1];
// bone->mNumWeights = 1;
// bone->mWeights->mVertexId = 0;
// bone->mWeights->mWeight = 0.f;
// }
// mesh->mBones[i] = bone;
std::vector<std::vector<aiVertexWeight>> weighting(node.skin->jointNames.size());
BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting);
for (uint32_t i = 0; i < mesh->mNumBones; ++i) {
aiBone* bone = new aiBone();
Ref<Node> joint = node.skin->jointNames[i];
if (!joint->name.empty()) {
bone->mName = joint->name;
} else {
// Assimp expects each bone to have a unique name.
static const std::string kDefaultName = "bone_";
char postfix[10] = {0};
ASSIMP_itoa10(postfix, i);
bone->mName = (kDefaultName + postfix);
}
GetNodeTransform(bone->mOffsetMatrix, *joint);
std::vector<aiVertexWeight>& weights = weighting[i];
bone->mNumWeights = static_cast<uint32_t>(weights.size());
if (bone->mNumWeights > 0) {
bone->mWeights = new aiVertexWeight[bone->mNumWeights];
memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
} else {
// Assimp expects all bones to have at least 1 weight.
bone->mWeights = new aiVertexWeight[1];
bone->mNumWeights = 1;
bone->mWeights->mVertexId = 0;
bone->mWeights->mWeight = 0.f;
}
mesh->mBones[i] = bone;
// CONFFX_BEGIN
// Assimp doesn't support bones with no weight. We have to count the
// number of bones that affect the mesh and limit it to just those bones.
int numBones = 0;
for (size_t i = 0; i < node.skin->jointNames.size(); ++i) {
if (!weighting[i].empty())
++numBones;
}
mesh->mNumBones = numBones;
if (numBones > 0)
{
mesh->mBones = new aiBone*[mesh->mNumBones];
int j = 0;
for (size_t i = 0; i < node.skin->jointNames.size(); ++i) {
if (!weighting[i].empty())
{
aiBone* bone = new aiBone();
Ref<Node> joint = node.skin->jointNames[i];
bone->mName = joint->name.empty() ? joint->id : joint->name;
// Get the inverseBindMatrix for the joint, grab the position out of row 4,
// invert the matrix and put the position back as column 4.
aiMatrix4x4 *tmpMat;
uint8_t *matPtr = node.skin->inverseBindMatrices->GetPointer();
tmpMat = (aiMatrix4x4*)matPtr;
bone->mOffsetMatrix = tmpMat[i];
aiVector3D tmpPos(bone->mOffsetMatrix.d1, bone->mOffsetMatrix.d2, bone->mOffsetMatrix.d3);
bone->mOffsetMatrix.d1 = bone->mOffsetMatrix.d2 = bone->mOffsetMatrix.d3 = 0.0;
bone->mOffsetMatrix.Inverse();
bone->mOffsetMatrix.a4 = tmpPos.x;
bone->mOffsetMatrix.b4 = tmpPos.y;
bone->mOffsetMatrix.c4 = tmpPos.z;
std::vector<aiVertexWeight>& weights = weighting[i];
bone->mNumWeights = static_cast<uint32_t>(weights.size());
if (bone->mNumWeights > 0) {
bone->mWeights = new aiVertexWeight[bone->mNumWeights];
memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
}
mesh->mBones[j++] = bone;
}
}
}
else
mesh->mBones = nullptr;
// CONFFX_END
//T3D_CHANGE_END
}
}
@ -921,7 +978,10 @@ struct AnimationSamplers {
aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers)
{
aiNodeAnim* anim = new aiNodeAnim();
anim->mNodeName = node.name;
//T3D_CHANGE_BEGIN
//anim->mNodeName = node.name;
anim->mNodeName = node.name.empty() ? node.id : node.name;
//T3D_CHANGE_END
static const float kMillisecondsFromSeconds = 1000.f;
@ -1042,15 +1102,27 @@ void glTF2Importer::ImportAnimations(glTF2::Asset& r)
std::unordered_map<unsigned int, AnimationSamplers> samplers = GatherSamplers(anim);
ai_anim->mNumChannels = static_cast<uint32_t>(samplers.size());
//T3D_CHANGE_BEGIN
//ai_anim->mNumChannels = static_cast<uint32_t>(samplers.size());
//if (ai_anim->mNumChannels > 0) {
// ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels];
// int j = 0;
// for (auto& iter : samplers) {
// ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second);
// ++j;
// }
//}
ai_anim->mNumChannels = r.skins.Size() > 0 ? r.skins[0].jointNames.size() : 0;
if (ai_anim->mNumChannels > 0) {
ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels];
int j = 0;
for (auto& iter : samplers) {
ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second);
for (auto& iter : r.skins[0].jointNames) {
ai_anim->mChannels[j] = CreateNodeAnim(r, *iter, samplers[iter.GetIndex()]);
++j;
}
}
//T3D_CHANGE_END
// Use the latest keyframe for the duration of the animation
double maxDuration = 0;

View file

@ -345,8 +345,10 @@ inline void Buffer::Read(Value& obj, Asset& r)
inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset)
{
byteLength = length ? length : stream.FileSize();
//T3D_CHANGE_BEGIN
if ((byteLength + baseOffset) > stream.FileSize())
byteLength = stream.FileSize() - baseOffset;
//T3D_CHANGE_END
if (baseOffset) {
stream.Seek(baseOffset, aiOrigin_SET);

View file

@ -19,6 +19,7 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//#define TORQUE_PBR_MATERIALS
#include "platform/platform.h"
#include "ts/loader/appSequence.h"
@ -38,7 +39,7 @@ String AppMaterial::cleanString(const String& str)
String cleanStr(str);
// Replace invalid characters with underscores
const String badChars(" -,.+=*/");
const String badChars(" -,.+=*/[]%$~;:");
for (String::SizeType i = 0; i < badChars.length(); i++)
cleanStr.replace(badChars[i], '_');
@ -52,50 +53,27 @@ String AppMaterial::cleanString(const String& str)
AssimpAppMaterial::AssimpAppMaterial(const char* matName)
{
name = matName;
diffuseColor = LinearColorF::ONE;
specularColor = LinearColorF::ONE;
specularPower = 0.8f;
doubleSided = false;
// Set some defaults
flags |= TSMaterialList::S_Wrap;
flags |= TSMaterialList::T_Wrap;
}
AssimpAppMaterial::AssimpAppMaterial(const struct aiMaterial* mtl)
AssimpAppMaterial::AssimpAppMaterial(aiMaterial* mtl) :
mAIMat(mtl)
{
aiString matName;
mtl->Get(AI_MATKEY_NAME, matName);
name = matName.C_Str();
if ( name.isEmpty() )
name = "defaultMaterial";
Con::printf("[ASSIMP] Loaded Material: %s", matName.C_Str());
// Opacity
F32 opacity = 0.0f;
mtl->Get(AI_MATKEY_OPACITY, opacity);
// Diffuse color
aiColor3D diff_color (0.f, 0.f, 0.f);
mtl->Get(AI_MATKEY_COLOR_DIFFUSE, diff_color);
diffuseColor = LinearColorF(diff_color.r, diff_color.g, diff_color.b, opacity);
// Spec Color color
aiColor3D spec_color (0.f, 0.f, 0.f);
mtl->Get(AI_MATKEY_COLOR_DIFFUSE, spec_color );
specularColor = LinearColorF(spec_color.r, spec_color.g, spec_color.b, 1.0f);
// Specular Power
mtl->Get(AI_MATKEY_SHININESS_STRENGTH, specularPower);
// Double-Sided
S32 dbl_sided = 0;
mtl->Get(AI_MATKEY_TWOSIDED, dbl_sided);
doubleSided = (dbl_sided != 0);
// Set some defaults
flags |= TSMaterialList::S_Wrap;
flags |= TSMaterialList::T_Wrap;
if (name.isEmpty())
{
name = cleanString(TSShapeLoader::getShapePath().getFileName());;
name += "_defMat";
}
Con::printf("[ASSIMP] Loading Material: %s", name.c_str());
#ifdef TORQUE_DEBUG
enumerateMaterialProperties(mtl);
#endif
}
Material* AssimpAppMaterial::createMaterial(const Torque::Path& path) const
@ -105,34 +83,264 @@ Material* AssimpAppMaterial::createMaterial(const Torque::Path& path) const
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() );
Material *newMat = MATMGR->allocateAndRegister(cleanName, getName());
Con::setVariable("$Con::File", oldScriptFile); // restore script path
newMat->mDiffuseMapFilename[0] = "";
newMat->mNormalMapFilename[0] = "";
newMat->mSpecularMapFilename[0] = "";
newMat->mDiffuse[0] = diffuseColor;
//newMat->mSpecular[0] = specularColor;
//newMat->mSpecularPower[0] = specularPower;
newMat->mDoubleSided = doubleSided;
newMat->mTranslucent = (bool)(flags & TSMaterialList::Translucent);
newMat->mTranslucentBlendOp = blendOp;
initMaterial(path, newMat);
return newMat;
}
}
void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) const
{
String cleanFile = cleanString(TSShapeLoader::getShapePath().getFileName());
String cleanName = cleanString(getName());
// Determine the blend mode and transparency for this material
Material::BlendOp blendOp = Material::None;
bool translucent = false;
float opacity = 1.0f;
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_OPACITY, opacity))
{
if (opacity != 1.0f)
{
translucent = true;
int blendInt;
blendOp = Material::LerpAlpha;
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_BLEND_FUNC, blendInt))
{
if (blendInt == aiBlendMode_Additive)
blendOp = Material::Add;
}
}
}
else
{ // No opacity key, see if it's defined as a gltf property
aiString opacityMode;
if (AI_SUCCESS == mAIMat->Get("$mat.gltf.alphaMode", 0, 0, opacityMode))
{
if (dStrcmp("MASK", opacityMode.C_Str()) == 0)
{
translucent = true;
blendOp = Material::LerpAlpha;
float cutoff;
if (AI_SUCCESS == mAIMat->Get("$mat.gltf.alphaCutoff", 0, 0, cutoff))
{
mat->mAlphaRef = (U32)(cutoff * 255); // alpha ref 0-255
mat->mAlphaTest = true;
}
}
else if (dStrcmp("OPAQUE", opacityMode.C_Str()) != 0)
{
translucent = true;
blendOp = Material::LerpAlpha;
}
}
}
mat->mTranslucent = translucent;
mat->mTranslucentBlendOp = blendOp;
// Assign color values.
LinearColorF diffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
aiColor3D read_color(1.f, 1.f, 1.f);
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_COLOR_DIFFUSE, read_color))
diffuseColor.set(read_color.r, read_color.g, read_color.b, opacity);
mat->mDiffuse[0] = diffuseColor;
aiString texName;
String torquePath;
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), texName))
{
torquePath = texName.C_Str();
if (!torquePath.isEmpty())
mat->mDiffuseMapFilename[0] = cleanTextureName(torquePath, cleanFile);
}
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0), texName))
{
torquePath = texName.C_Str();
if (!torquePath.isEmpty())
mat->mNormalMapFilename[0] = cleanTextureName(torquePath, cleanFile);
}
#ifdef TORQUE_PBR_MATERIALS
float floatVal;
if (AI_SUCCESS == mAIMat->Get("$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0, floatVal))
{ // The shape has pbr material definitions
String aoName, rmName; // occlusion and roughness/metalness maps
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0), texName))
aoName = texName.C_Str();
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_UNKNOWN, 0), texName))
rmName = texName.C_Str();
//if (aoName.isNotEmpty() && (aoName == rmName))
// mat->mOrmMapFilename[0] = cleanTextureName(aoName, cleanFile); // It's an ORM map
//else if (aoName.isNotEmpty() || rmName.isNotEmpty())
if (aoName.isNotEmpty() || rmName.isNotEmpty())
{ // If we have either map, fill all three slots
if (rmName.isNotEmpty())
{
mat->mRoughMapFilename[0] = cleanTextureName(rmName, cleanFile); // Roughness
mat->mSmoothnessChan[0] = 1.0f;
mat->mInvertSmoothness = (floatVal == 1.0f);
mat->mMetalMapFilename[0] = cleanTextureName(rmName, cleanFile); // Metallic
mat->mMetalChan[0] = 2.0f;
}
if (aoName.isNotEmpty())
{
mat->mAOMapFilename[0] = cleanTextureName(aoName, cleanFile); // occlusion
mat->mAOChan[0] = 0.0f;
}
else
{
mat->mAOMapFilename[0] = cleanTextureName(rmName, cleanFile); // occlusion
mat->mAOChan[0] = 0.0f;
}
}
}
#else
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, 0), texName))
{
torquePath = texName.C_Str();
if (!torquePath.isEmpty())
mat->mSpecularMapFilename[0] = cleanTextureName(torquePath, cleanFile);
}
LinearColorF specularColor(1.0f, 1.0f, 1.0f, 1.0f);
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_COLOR_SPECULAR, read_color))
specularColor.set(read_color.r, read_color.g, read_color.b, opacity);
mat->mSpecular[0] = specularColor;
// Specular Power
F32 specularPower = 1.0f;
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_SHININESS_STRENGTH, specularPower))
mat->mSpecularPower[0] = specularPower;
// Specular
F32 specularStrength = 0.0f;
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_SHININESS, specularStrength))
mat->mSpecularStrength[0] = specularStrength;
#endif
// Double-Sided
bool doubleSided = false;
S32 dbl_sided = 0;
if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TWOSIDED, dbl_sided))
doubleSided = (dbl_sided != 0);
mat->mDoubleSided = doubleSided;
}
String AssimpAppMaterial::cleanTextureName(String& texName, String& shapeName)
{
String cleanStr;
if (texName[0] == '*')
{
cleanStr = shapeName;
cleanStr += "_cachedTex";
cleanStr += texName.substr(1);
}
else
{
cleanStr = texName;
cleanStr.replace('\\', '/');
}
return cleanStr;
}
#ifdef TORQUE_DEBUG
void AssimpAppMaterial::enumerateMaterialProperties(aiMaterial* mtl)
{
for (U32 i = 0; i < mtl->mNumProperties; ++i)
{
aiMaterialProperty* matProp = mtl->mProperties[i];
String outText;
if (matProp)
{
outText = String::ToString(" Key: %s, Index: %d, Semantic: ", matProp->mKey.C_Str(), matProp->mIndex);
switch (matProp->mSemantic)
{
case aiTextureType_NONE:
outText += "aiTextureType_NONE";
break;
case aiTextureType_DIFFUSE:
outText += "aiTextureType_DIFFUSE";
break;
case aiTextureType_SPECULAR:
outText += "aiTextureType_SPECULAR";
break;
case aiTextureType_AMBIENT:
outText += "aiTextureType_AMBIENT";
break;
case aiTextureType_EMISSIVE:
outText += "aiTextureType_EMISSIVE";
break;
case aiTextureType_HEIGHT:
outText += "aiTextureType_HEIGHT";
break;
case aiTextureType_NORMALS:
outText += "aiTextureType_NORMALS";
break;
case aiTextureType_SHININESS:
outText += "aiTextureType_SHININESS";
break;
case aiTextureType_OPACITY:
outText += "aiTextureType_OPACITY";
break;
case aiTextureType_DISPLACEMENT:
outText += "aiTextureType_DISPLACEMENT";
break;
case aiTextureType_LIGHTMAP:
outText += "aiTextureType_LIGHTMAP";
break;
case aiTextureType_REFLECTION:
outText += "aiTextureType_REFLECTION";
break;
default:
outText += "aiTextureType_UNKNOWN";
break;
}
aiString stringProp;
F32* floatProp;
double* doubleProp;
S32* intProp;
switch (matProp->mType)
{
case aiPTI_Float:
floatProp = (F32*)matProp->mData;
for (U32 j = 0; j < matProp->mDataLength / sizeof(F32); ++j)
outText += String::ToString(", %0.4f", floatProp[j]);
break;
case aiPTI_Double:
doubleProp = (double*)matProp->mData;
for (U32 j = 0; j < matProp->mDataLength / sizeof(double); ++j)
outText += String::ToString(", %0.4lf", doubleProp[j]);
break;
case aiPTI_String:
aiGetMaterialString(mtl, matProp->mKey.C_Str(), matProp->mSemantic, matProp->mIndex, &stringProp);
outText += String::ToString(", %s", stringProp.C_Str());
break;
case aiPTI_Integer:
intProp = (S32*)matProp->mData;
for (U32 j = 0; j < matProp->mDataLength / sizeof(S32); ++j)
outText += String::ToString(", %d", intProp[j]);
break;
case aiPTI_Buffer:
outText += ", aiPTI_Buffer format data";
break;
default:
outText += ", Unknown data type";
}
Con::printf("%s", outText.c_str());
}
}
}
#endif

View file

@ -26,6 +26,7 @@
#ifndef _APPMATERIAL_H_
#include "ts/loader/appMaterial.h"
#endif
#include <assimp/scene.h>
class Material;
@ -34,18 +35,22 @@ class AssimpAppMaterial : public AppMaterial
typedef AppMaterial Parent;
String name;
LinearColorF diffuseColor;
LinearColorF specularColor;
F32 specularPower;
bool doubleSided;
aiMaterial* mAIMat;
#ifdef TORQUE_DEBUG
void enumerateMaterialProperties(aiMaterial* mtl);
#endif
static String cleanTextureName(String& texName, String& shapeName);
public:
AssimpAppMaterial(const char* matName);
AssimpAppMaterial(const struct aiMaterial* mtl);
AssimpAppMaterial(aiMaterial* mtl);
~AssimpAppMaterial() { }
String getName() const { return name; }
Material* createMaterial(const Torque::Path& path) const;
void initMaterial(const Torque::Path& path, Material* mat) const;
};
#endif // _ASSIMP_APPMATERIAL_H_

View file

@ -202,6 +202,15 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
MatrixF boneTransform;
AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform);
Point3F boneScale = boneTransform.getScale();
if (boneScale != Point3F::One)
{
Point3F scaleMult = Point3F::One / boneScale;
Point3F scalePos = boneTransform.getPosition();
boneTransform.scale(scaleMult);
scalePos /= scaleMult;
boneTransform.setPosition(scalePos);
}
initialTransforms.push_back(boneTransform);
//Weights
@ -225,20 +234,32 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
vertexIndex.setSize(nonZeroWeights);
boneIndex.setSize(nonZeroWeights);
// Copy the weights to our vectors in vertex order
// Copy the weights to our vectors in vertex order and
// normalize vertex weights (force weights for each vert to sum to 1)
U32 nextWeight = 0;
for (U32 i = 0; i < mMeshData->mNumVertices; i++)
for (U32 i = 0; i < mMeshData->mNumVertices; ++i)
{
for (U32 ind = 0; ind < nonZeroWeights; ind++)
U32 vertStart = nextWeight;
F32 invTotalWeight = 0;
for (U32 ind = 0; ind < nonZeroWeights; ++ind)
{
if (tmpVertexIndex[ind] == i)
{
weight[nextWeight] = tmpWeight[ind];
invTotalWeight += tmpWeight[ind];
vertexIndex[nextWeight] = tmpVertexIndex[ind];
boneIndex[nextWeight] = tmpBoneIndex[ind];
nextWeight++;
}
}
// Now normalize the vertex weights
if (invTotalWeight > 0.0)
{
invTotalWeight = 1.0f / invTotalWeight;
for (U32 ind = vertStart; ind < nextWeight; ++ind)
weight[ind] *= invTotalWeight;
}
}
if ( noUVFound )

View file

@ -32,6 +32,7 @@
#include <assimp/types.h>
aiAnimation* AssimpAppNode::sActiveSequence = NULL;
F32 AssimpAppNode::sTimeMultiplier = 1.0f;
AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent)
: mInvertMeshes(false),
@ -132,15 +133,15 @@ void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animS
F32 lastT = 0.0;
for (U32 key = 0; key < nodeAnim->mNumPositionKeys; ++key)
{
F32 curT = (F32)nodeAnim->mPositionKeys[key].mTime;
F32 curT = sTimeMultiplier * (F32)nodeAnim->mPositionKeys[key].mTime;
curPos.set(nodeAnim->mPositionKeys[key].mValue.x, nodeAnim->mPositionKeys[key].mValue.y, nodeAnim->mPositionKeys[key].mValue.z);
if (curT > t)
if ((curT > t) && (key > 0))
{
F32 factor = (t - lastT) / (curT - lastT);
trans.interpolate(lastPos, curPos, factor);
break;
}
else if ((curT == t) || (key == nodeAnim->mNumPositionKeys - 1))
else if ((curT >= t) || (key == nodeAnim->mNumPositionKeys - 1))
{
trans = curPos;
break;
@ -161,16 +162,16 @@ void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animS
F32 lastT = 0.0;
for (U32 key = 0; key < nodeAnim->mNumRotationKeys; ++key)
{
F32 curT = (F32)nodeAnim->mRotationKeys[key].mTime;
F32 curT = sTimeMultiplier * (F32)nodeAnim->mRotationKeys[key].mTime;
curRot.set(nodeAnim->mRotationKeys[key].mValue.x, nodeAnim->mRotationKeys[key].mValue.y,
nodeAnim->mRotationKeys[key].mValue.z, nodeAnim->mRotationKeys[key].mValue.w);
if (curT > t)
if ((curT > t) && (key > 0))
{
F32 factor = (t - lastT) / (curT - lastT);
rot.interpolate(lastRot, curRot, factor);
break;
}
else if ((curT == t) || (key == nodeAnim->mNumRotationKeys - 1))
else if ((curT >= t) || (key == nodeAnim->mNumRotationKeys - 1))
{
rot = curRot;
break;
@ -190,15 +191,15 @@ void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animS
F32 lastT = 0.0;
for (U32 key = 0; key < nodeAnim->mNumScalingKeys; ++key)
{
F32 curT = (F32)nodeAnim->mScalingKeys[key].mTime;
F32 curT = sTimeMultiplier * (F32)nodeAnim->mScalingKeys[key].mTime;
curScale.set(nodeAnim->mScalingKeys[key].mValue.x, nodeAnim->mScalingKeys[key].mValue.y, nodeAnim->mScalingKeys[key].mValue.z);
if (curT > t)
if ((curT > t) && (key > 0))
{
F32 factor = (t - lastT) / (curT - lastT);
scale.interpolate(lastScale, curScale, factor);
break;
}
else if ((curT == t) || (key == nodeAnim->mNumScalingKeys - 1))
else if ((curT >= t) || (key == nodeAnim->mNumScalingKeys - 1))
{
scale = curScale;
break;

View file

@ -70,6 +70,7 @@ public:
}
static aiAnimation* sActiveSequence;
static F32 sTimeMultiplier;
//-----------------------------------------------------------------------
const char *getName() { return mName; }

View file

@ -16,19 +16,24 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) :
seqStart(0.0f),
mAnim(a)
{
// From: http://sir-kimmi.de/assimp/lib_html/data.html#anims
// An aiAnimation has a duration. The duration as well as all time stamps are given in ticks.
// To get the correct timing, all time stamp thus have to be divided by aiAnimation::mTicksPerSecond.
// Beware, though, that certain combinations of file format and exporter don't always store this
// information in the exported file. In this case, mTicksPerSecond is set to 0 to indicate the lack of knowledge.
mSequenceName = mAnim->mName.C_Str();
if (mSequenceName.isEmpty())
mSequenceName = "ambient";
Con::printf("\n[Assimp] Adding %s animation", mSequenceName.c_str());
fps = (mAnim->mTicksPerSecond > 0) ? mAnim->mTicksPerSecond : 30.0f;
U32 maxKeys = 0;
F32 maxEndTime = 0;
F32 minFrameTime = 1000.0f;
F32 minFrameTime = 100000.0f;
// Detect the frame rate (minimum time between keyframes) and max sequence time
for (U32 i = 0; i < mAnim->mNumChannels; ++i)
{
aiNodeAnim *nodeAnim = mAnim->mChannels[i];
maxKeys = getMax(maxKeys, nodeAnim->mNumPositionKeys);
maxKeys = getMax(maxKeys, nodeAnim->mNumRotationKeys);
maxKeys = getMax(maxKeys, nodeAnim->mNumScalingKeys);
if (nodeAnim->mNumPositionKeys)
maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mPositionKeys[nodeAnim->mNumPositionKeys-1].mTime);
if (nodeAnim->mNumRotationKeys)
@ -53,9 +58,31 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) :
}
}
fps = (minFrameTime > 0.0f) ? 1.0f / minFrameTime : fps;
fps = mClamp(fps, TSShapeLoader::MinFrameRate, TSShapeLoader::MaxFrameRate);
seqEnd = maxEndTime;
S32 timeFactor = Con::getIntVariable("$Assimp::AnimTiming", 1);
S32 fpsRequest = Con::getIntVariable("$Assimp::AnimFPS", 30);
if (timeFactor == 0)
{ // Timing specified in frames
fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate);
maxKeys = getMax(maxKeys, (U32)maxEndTime); // Keys won't be assigned for every frame.
seqEnd = maxKeys / fps;
mTimeMultiplier = 1.0f / fps;
}
else
{ // Timing specified in seconds or ms depending on format
if (maxEndTime > 1000.0f || mAnim->mDuration > 1000.0f)
{
timeFactor = 1000.0f; // If it's more than 1000 seconds, assume it's ms.
Con::setIntVariable("$Assimp::AnimTiming", 1000);
}
timeFactor = mClamp(timeFactor, 1, 1000);
minFrameTime /= (F32)timeFactor;
maxEndTime /= (F32)timeFactor;
fps = (minFrameTime > 0.0f) ? 1.0f / minFrameTime : fps;
fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate);
seqEnd = maxEndTime;
mTimeMultiplier = 1.0f / timeFactor;
}
}
AssimpAppSequence::~AssimpAppSequence()
@ -65,7 +92,10 @@ AssimpAppSequence::~AssimpAppSequence()
void AssimpAppSequence::setActive(bool active)
{
if (active)
{
AssimpAppNode::sActiveSequence = mAnim;
AssimpAppNode::sTimeMultiplier = mTimeMultiplier;
}
else
{
if (AssimpAppNode::sActiveSequence == mAnim)

View file

@ -22,8 +22,10 @@
class AssimpAppSequence : public AppSequence
{
String mSequenceName;
F32 seqStart;
F32 seqEnd;
F32 mTimeMultiplier; // The factor needed to convert the sequence data timestamp to seconds
public:
@ -37,7 +39,7 @@ public:
virtual S32 getNumTriggers() const { return 0; }
virtual void getTrigger(S32 index, TSShape::Trigger& trigger) const { trigger.state = 0; }
virtual const char* getName() const { return mAnim->mName.C_Str(); }
virtual const char* getName() const { return mSequenceName.c_str(); }
F32 getStart() const { return seqStart; }
F32 getEnd() const { return seqEnd; }

View file

@ -200,6 +200,10 @@ void AssimpShapeLoader::enumerateScene()
Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes);
Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials);
// Extract embedded textures
for (U32 i = 0; i < mScene->mNumTextures; ++i)
extractTexture(i, mScene->mTextures[i]);
// Load all the materials.
for ( U32 i = 0; i < mScene->mNumMaterials; i++ )
AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i]));
@ -302,8 +306,11 @@ void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) )
{
// Only update existing materials if forced to
if ( ColladaUtils::getOptions().forceUpdateMaterials )
persistMgr.setDirty( mappedMat );
if (Con::getBoolVariable("$Assimp::ForceUpdateMats", false))
{
mat->initMaterial(scriptPath, mappedMat);
persistMgr.setDirty(mappedMat);
}
}
else
{
@ -322,8 +329,6 @@ void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
/// Check if an up-to-date cached DTS is available for this DAE file
bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path)
{
return false;
// Generate the cached filename
Torque::Path cachedPath(path);
cachedPath.setExtension("cached.dts");
@ -332,13 +337,13 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path)
FileTime cachedModifyTime;
if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime))
{
bool forceLoadDAE = Con::getBoolVariable("$assimp::forceLoad", false);
bool forceLoad = Con::getBoolVariable("$assimp::forceLoad", false);
FileTime daeModifyTime;
if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) ||
(!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) ))
(!forceLoad && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) ))
{
// DAE not found, or cached DTS is newer
// Original file not found, or cached DTS is newer
return true;
}
}
@ -393,6 +398,56 @@ void AssimpShapeLoader::detectDetails()
AssimpAppMesh::fixDetailSize(singleDetail, Con::getIntVariable("$Assimp::singleDetailSize", 2));
}
void AssimpShapeLoader::extractTexture(U32 index, aiTexture* pTex)
{ // Cache an embedded texture to disk
updateProgress(Load_EnumerateScene, "Extracting Textures...", mScene->mNumTextures, index);
Con::printf("[Assimp] Extracting Texture %s, W: %d, H: %d, %d of %d, format hint: (%s)", pTex->mFilename.C_Str(),
pTex->mWidth, pTex->mHeight, index, mScene->mNumTextures, pTex->achFormatHint);
// Create the texture filename
String cleanFile = AppMaterial::cleanString(TSShapeLoader::getShapePath().getFileName());
String texName = String::ToString("%s_cachedTex%d", cleanFile.c_str(), index);
Torque::Path texPath = shapePath;
texPath.setFileName(texName);
if (pTex->mHeight == 0)
{ // Compressed format, write the data directly to disc
texPath.setExtension(pTex->achFormatHint);
FileStream *outputStream;
if ((outputStream = FileStream::createAndOpen(texPath.getFullPath(), Torque::FS::File::Write)) != NULL)
{
outputStream->setPosition(0);
outputStream->write(pTex->mWidth, pTex->pcData);
outputStream->close();
delete outputStream;
}
}
else
{ // Embedded pixel data, fill a bitmap and save it.
GFXTexHandle shapeTex;
shapeTex.set(pTex->mWidth, pTex->mHeight, GFXFormatR8G8B8A8_SRGB, &GFXDynamicTextureSRGBProfile,
String::ToString("AssimpShapeLoader (%s:%i)", __FILE__, __LINE__), 1, 0);
GFXLockedRect *rect = shapeTex.lock();
for (U32 y = 0; y < pTex->mHeight; ++y)
{
for (U32 x = 0; x < pTex->mWidth; ++x)
{
U32 targetIndex = (y * rect->pitch) + (x * 4);
U32 sourceIndex = ((y * pTex->mWidth) + x) * 4;
rect->bits[targetIndex] = pTex->pcData[sourceIndex].r;
rect->bits[targetIndex + 1] = pTex->pcData[sourceIndex].g;
rect->bits[targetIndex + 2] = pTex->pcData[sourceIndex].b;
rect->bits[targetIndex + 3] = pTex->pcData[sourceIndex].a;
}
}
shapeTex.unlock();
texPath.setExtension("png");
shapeTex->dumpToDisk("PNG", texPath.getFullPath());
}
}
//-----------------------------------------------------------------------------
/// This function is invoked by the resource manager based on file extension.
TSShape* assimpLoadShape(const Torque::Path &path)

View file

@ -26,6 +26,7 @@
#ifndef _TSSHAPELOADER_H_
#include "ts/loader/tsShapeLoader.h"
#endif
#include <assimp/texture.h>
//-----------------------------------------------------------------------------
class AssimpShapeLoader : public TSShapeLoader
@ -37,6 +38,7 @@ protected:
virtual bool ignoreNode(const String& name);
void detectDetails();
void extractTexture(U32 index, aiTexture* pTex);
public:
AssimpShapeLoader();

View file

@ -393,6 +393,107 @@
canSaveDynamicFields = "0";
};
new GuiTextCtrl() {
text = "Animation Timing:";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiTextRightProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "10 311";
Extent = "85 16";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
hovertime = "1000";
canSaveDynamicFields = "0";
};
new GuiPopUpMenuCtrl() {
maxPopupHeight = "200";
sbUsesNAColor = "0";
reverseTextList = "0";
bitmapBounds = "16 16";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiPopUpMenuProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "100 310";
Extent = "86 18";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
ToolTip = "Select the timing units used in the animation data.";
hovertime = "1000";
internalName = "animTiming";
canSaveDynamicFields = "0";
};
new GuiTextCtrl() {
text = "FPS:";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiTextRightProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "200 311";
Extent = "20 16";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
hovertime = "1000";
canSaveDynamicFields = "0";
};
new GuiTextEditCtrl() {
historySize = "0";
password = "0";
tabComplete = "0";
sinkAllKeyEvents = "0";
passwordMask = "*";
text = "2";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiTextEditProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "225 310";
Extent = "26 18";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
ToolTip = "Frames per second for all animations when Animation Timing type is Frames (5 - 60)";
hovertime = "1000";
internalName = "animFPS";
canSaveDynamicFields = "0";
};
new GuiTextCtrl() {
text = "LOD";
maxLength = "1024";
@ -582,6 +683,28 @@
canSaveDynamicFields = "0";
};
new GuiCheckBoxCtrl() {
useInactiveState = "0";
text = " Force update materials.cs";
groupNum = "-1";
buttonType = "ToggleButton";
useMouseEvents = "0";
isContainer = "0";
Profile = "ToolsGuiCheckBoxProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "210 150";
Extent = "200 13";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
variable = "$Assimp::ForceUpdateMats";
tooltipprofile = "ToolsGuiToolTipProfile";
ToolTip = "Forces update of materials.cs (even if Materials already exist)";
hovertime = "1000";
canSaveDynamicFields = "0";
};
new GuiButtonCtrl() {
text = "OK";
groupNum = "-1";
@ -643,6 +766,10 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd)
$Assimp::FindDegenerates = true;
$Assimp::FindInvalidData = true;
$Assimp::JoinIdenticalVertices = true;
$Assimp::FlipNormals = false;
$Assimp::AnimTiming = 1; // Seconds
$Assimp::AnimFPS = 30; // Framerate when timing is frames.
}
%this-->upAxis.clear();
@ -656,9 +783,15 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd)
%this-->lodType.add("SingleSize", 1);
%this-->lodType.add("TrailingNumber", 2);
%this-->lodType.setSelected($Assimp::lodType);
%this-->singleDetailSize.text = $Assimp::singleDetailSize;
%this-->animTiming.clear();
%this-->animTiming.add("Frames", 0);
%this-->animTiming.add("Seconds", 1);
%this-->animTiming.add("Milliseconds", 1000);
%this-->animTiming.setSelected($Assimp::AnimTiming);
%this-->animFPS.text = $Assimp::AnimFPS;
//Triangulate is a default(currently mandatory) behavior
$Assimp::Triangulate = true;
@ -681,6 +814,9 @@ function AssimpImportDlg::onOK(%this)
$Assimp::lodType = %this-->lodType.getSelected();
$Assimp::singleDetailSize = %this-->singleDetailSize.getText();
$Assimp::AnimTiming = %this-->animTiming.getSelected();
$Assimp::AnimFPS = %this-->animFPS.getText();
// Load the shape (always from the DAE)
$assimp::forceLoad = true;
eval(%this.cmd);

View file

@ -261,13 +261,13 @@ function ShapeEdSelectWindow::onSelect( %this, %path )
// Prompt user to save the old shape if it is dirty
if ( ShapeEditor.isDirty() )
{
%cmd = "ColladaImportDlg.showDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", ";
%cmd = "showImportDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", ";
MessageBoxYesNoCancel( "Shape Modified", "Would you like to save your changes?", %cmd @ "true );\" );", %cmd @ "false );\" );" );
}
else
{
%cmd = "ShapeEditor.selectShape( \"" @ %path @ "\", false );";
ColladaImportDlg.showDialog( %path, %cmd );
showImportDialog( %path, %cmd );
}
}

View file

@ -393,6 +393,107 @@
canSaveDynamicFields = "0";
};
new GuiTextCtrl() {
text = "Animation Timing:";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiTextRightProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "10 311";
Extent = "85 16";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
hovertime = "1000";
canSaveDynamicFields = "0";
};
new GuiPopUpMenuCtrl() {
maxPopupHeight = "200";
sbUsesNAColor = "0";
reverseTextList = "0";
bitmapBounds = "16 16";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiPopUpMenuProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "100 310";
Extent = "86 18";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
ToolTip = "Select the timing units used in the animation data.";
hovertime = "1000";
internalName = "animTiming";
canSaveDynamicFields = "0";
};
new GuiTextCtrl() {
text = "FPS:";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiTextRightProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "200 311";
Extent = "20 16";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
hovertime = "1000";
canSaveDynamicFields = "0";
};
new GuiTextEditCtrl() {
historySize = "0";
password = "0";
tabComplete = "0";
sinkAllKeyEvents = "0";
passwordMask = "*";
text = "2";
maxLength = "1024";
Margin = "0 0 0 0";
Padding = "0 0 0 0";
AnchorTop = "1";
AnchorBottom = "0";
AnchorLeft = "1";
AnchorRight = "0";
isContainer = "0";
Profile = "ToolsGuiTextEditProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "225 310";
Extent = "26 18";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
tooltipprofile = "ToolsGuiToolTipProfile";
ToolTip = "Frames per second for all animations when Animation Timing type is Frames (5 - 60)";
hovertime = "1000";
internalName = "animFPS";
canSaveDynamicFields = "0";
};
new GuiTextCtrl() {
text = "LOD";
maxLength = "1024";
@ -582,6 +683,28 @@
canSaveDynamicFields = "0";
};
new GuiCheckBoxCtrl() {
useInactiveState = "0";
text = " Force update materials.cs";
groupNum = "-1";
buttonType = "ToggleButton";
useMouseEvents = "0";
isContainer = "0";
Profile = "ToolsGuiCheckBoxProfile";
HorizSizing = "right";
VertSizing = "bottom";
position = "210 150";
Extent = "200 13";
MinExtent = "8 2";
canSave = "1";
Visible = "1";
variable = "$Assimp::ForceUpdateMats";
tooltipprofile = "ToolsGuiToolTipProfile";
ToolTip = "Forces update of materials.cs (even if Materials already exist)";
hovertime = "1000";
canSaveDynamicFields = "0";
};
new GuiButtonCtrl() {
text = "OK";
groupNum = "-1";
@ -643,6 +766,10 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd)
$Assimp::FindDegenerates = true;
$Assimp::FindInvalidData = true;
$Assimp::JoinIdenticalVertices = true;
$Assimp::FlipNormals = false;
$Assimp::AnimTiming = 1; // Seconds
$Assimp::AnimFPS = 30; // Framerate when timing is frames.
}
%this-->upAxis.clear();
@ -656,9 +783,15 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd)
%this-->lodType.add("SingleSize", 1);
%this-->lodType.add("TrailingNumber", 2);
%this-->lodType.setSelected($Assimp::lodType);
%this-->singleDetailSize.text = $Assimp::singleDetailSize;
%this-->animTiming.clear();
%this-->animTiming.add("Frames", 0);
%this-->animTiming.add("Seconds", 1);
%this-->animTiming.add("Milliseconds", 1000);
%this-->animTiming.setSelected($Assimp::AnimTiming);
%this-->animFPS.text = $Assimp::AnimFPS;
//Triangulate is a default(currently mandatory) behavior
$Assimp::Triangulate = true;
@ -681,6 +814,9 @@ function AssimpImportDlg::onOK(%this)
$Assimp::lodType = %this-->lodType.getSelected();
$Assimp::singleDetailSize = %this-->singleDetailSize.getText();
$Assimp::AnimTiming = %this-->animTiming.getSelected();
$Assimp::AnimFPS = %this-->animFPS.getText();
// Load the shape (always from the DAE)
$assimp::forceLoad = true;
eval(%this.cmd);

View file

@ -261,7 +261,7 @@ function ShapeEdSelectWindow::onSelect( %this, %path )
// Prompt user to save the old shape if it is dirty
if ( ShapeEditor.isDirty() )
{
%cmd = "ColladaImportDlg.showDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", ";
%cmd = "showImportDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", ";
MessageBoxYesNoCancel( "Shape Modified", "Would you like to save your changes?", %cmd @ "true );\" );", %cmd @ "false );\" );" );
}
else