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,49 @@
//-----------------------------------------------------------------------------
// 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 _TSMESHINTRINSICS_ARCH_H_
#define _TSMESHINTRINSICS_ARCH_H_
#if defined(TORQUE_CPU_X86)
# // x86 CPU family implementations
extern void zero_vert_normal_bulk_SSE(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride);
extern void m_matF_x_BatchedVertWeightList_SSE(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride);
#if (_MSC_VER >= 1500)
extern void m_matF_x_BatchedVertWeightList_SSE4(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride);
#endif
#
#elif defined(TORQUE_CPU_PPC)
# // PPC CPU family implementations
# if defined(TORQUE_OS_XENON)
extern void zero_vert_normal_bulk_X360(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride);
extern void m_matF_x_BatchedVertWeightList_X360(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride);
# else
extern void zero_vert_normal_bulk_gccvec(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride);
extern void m_matF_x_BatchedVertWeightList_gccvec(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride);
# endif
#
#else
# // Other CPU types go here...
#endif
#endif // _TSMESHINTRINSICS_ARCH_H_

View file

@ -0,0 +1,183 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsMesh.h"
#if defined(TORQUE_CPU_X86)
#include "ts/tsMeshIntrinsics.h"
#include <xmmintrin.h>
void zero_vert_normal_bulk_SSE(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride)
{
// A U8 * version of the in/out pointer
register char *outData = reinterpret_cast<char *>(outPtr);
register __m128 vPos;
register __m128 vNrm;
register __m128 vMask;
const __m128 _point3f_zero_mask = { 0.0f, 0.0f, 0.0f, 1.0f };
vMask = _mm_load_ps((const F32*)&_point3f_zero_mask);
// pre-populate cache
for(int i = 0; i < 8; i++)
_mm_prefetch(reinterpret_cast<const char *>(outData + outStride * i), _MM_HINT_T0);
for(int i = 0; i < count; i++)
{
TSMesh::__TSMeshVertexBase *curElem = reinterpret_cast<TSMesh::__TSMeshVertexBase *>(outData);
// prefetch 8 items ahead (should really detect cache size or something)
_mm_prefetch(reinterpret_cast<const char *>(outData + outStride * 8), _MM_HINT_T0);
// load
vPos = _mm_load_ps(curElem->_vert);
vNrm = _mm_load_ps(curElem->_normal);
// mask
vPos = _mm_mul_ps(vPos, _point3f_zero_mask);
vNrm = _mm_mul_ps(vNrm, _point3f_zero_mask);
// store
_mm_store_ps(curElem->_vert, vPos);
_mm_store_ps(curElem->_normal, vNrm);
// update output pointer
outData += outStride;
}
}
//------------------------------------------------------------------------------
void m_matF_x_BatchedVertWeightList_SSE(const MatrixF &mat,
const dsize_t count,
const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch,
U8 * const __restrict outPtr,
const dsize_t outStride)
{
const char * __restrict iPtr = reinterpret_cast<const char *>(batch);
const dsize_t inStride = sizeof(TSSkinMesh::BatchData::BatchedVertWeight);
// SSE intrinsic version
// Based on: http://www.cortstratton.org/articles/HugiCode.html
// Load matrix, transposed, into registers
MatrixF transMat;
mat.transposeTo(transMat);
register __m128 sseMat[4];
sseMat[0] = _mm_loadu_ps(&transMat[0]);
sseMat[1] = _mm_loadu_ps(&transMat[4]);
sseMat[2] = _mm_loadu_ps(&transMat[8]);
sseMat[3] = _mm_loadu_ps(&transMat[12]);
// mask
const __m128 _w_mask = { 1.0f, 1.0f, 1.0f, 0.0f };
// temp registers
register __m128 tempPos;
register __m128 tempNrm;
register __m128 scratch0;
register __m128 scratch1;
register __m128 inPos;
register __m128 inNrm;
// pre-populate cache
const TSSkinMesh::BatchData::BatchedVertWeight &firstElem = batch[0];
for(int i = 0; i < 8; i++)
{
_mm_prefetch(reinterpret_cast<const char *>(iPtr + inStride * i), _MM_HINT_T0);
_mm_prefetch(reinterpret_cast<const char *>(outPtr + outStride * (i + firstElem.vidx)), _MM_HINT_T0);
}
for(register int i = 0; i < count; i++)
{
const TSSkinMesh::BatchData::BatchedVertWeight &inElem = batch[i];
TSMesh::__TSMeshVertexBase *outElem = reinterpret_cast<TSMesh::__TSMeshVertexBase *>(outPtr + inElem.vidx * outStride);
// process x (hiding the prefetches in the delays)
inPos = _mm_load_ps(inElem.vert);
inNrm = _mm_load_ps(inElem.normal);
// prefetch input
#define INPUT_PREFETCH_LOOKAHEAD 64
const char *prefetchInput = reinterpret_cast<const char *>(batch) + inStride * (i + INPUT_PREFETCH_LOOKAHEAD);
_mm_prefetch(prefetchInput, _MM_HINT_T0);
// propagate the .x elements across the vectors
tempPos = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(0, 0, 0, 0));
tempNrm = _mm_shuffle_ps(inNrm, inNrm, _MM_SHUFFLE(0, 0, 0, 0));
// prefetch ouput with half the lookahead distance of the input
#define OUTPUT_PREFETCH_LOOKAHEAD (INPUT_PREFETCH_LOOKAHEAD >> 1)
const char *outPrefetch = reinterpret_cast<const char*>(outPtr) + outStride * (inElem.vidx + OUTPUT_PREFETCH_LOOKAHEAD);
_mm_prefetch(outPrefetch, _MM_HINT_T0);
// mul by column 0
tempPos = _mm_mul_ps(tempPos, sseMat[0]);
tempNrm = _mm_mul_ps(tempNrm, sseMat[0]);
// process y
scratch0 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(1, 1, 1, 1));
scratch1 = _mm_shuffle_ps(inNrm, inNrm, _MM_SHUFFLE(1, 1, 1, 1));
scratch0 = _mm_mul_ps(scratch0, sseMat[1]);
scratch1 = _mm_mul_ps(scratch1, sseMat[1]);
tempPos = _mm_add_ps(tempPos, scratch0);
tempNrm = _mm_add_ps(tempNrm, scratch1);
// process z
scratch0 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(2, 2, 2, 2));
scratch1 = _mm_shuffle_ps(inNrm, inNrm, _MM_SHUFFLE(2, 2, 2, 2));
scratch0 = _mm_mul_ps(scratch0, sseMat[2]);
scratch1 = _mm_mul_ps(scratch1, sseMat[2]);
tempPos = _mm_add_ps(tempPos, scratch0);
inNrm = _mm_load_ps(outElem->_normal); //< load normal for accumulation
scratch0 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(3, 3, 3, 3));//< load bone weight across all elements of scratch0
tempNrm = _mm_add_ps(tempNrm, scratch1);
scratch0 = _mm_mul_ps(scratch0, _w_mask); //< mask off last
// Translate the position by adding the 4th column of the matrix to it
tempPos = _mm_add_ps(tempPos, sseMat[3]);
// now multiply by the blend weight, and mask out the W component of both vectors
tempPos = _mm_mul_ps(tempPos, scratch0);
tempNrm = _mm_mul_ps(tempNrm, scratch0);
inPos = _mm_load_ps(outElem->_vert); //< load position for accumulation
// accumulate with previous values
tempNrm = _mm_add_ps(tempNrm, inNrm);
tempPos = _mm_add_ps(tempPos, inPos);
_mm_store_ps(outElem->_vert, tempPos); //< output position
_mm_store_ps(outElem->_normal, tempNrm); //< output normal
}
}
#endif // TORQUE_CPU_X86

View file

@ -0,0 +1,108 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsMesh.h"
#if defined(TORQUE_CPU_X86) && (_MSC_VER >= 1500)
#include "ts/tsMeshIntrinsics.h"
#include <smmintrin.h>
void m_matF_x_BatchedVertWeightList_SSE4(const MatrixF &mat,
const dsize_t count,
const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch,
U8 * const __restrict outPtr,
const dsize_t outStride)
{
const char * __restrict iPtr = reinterpret_cast<const char *>(batch);
const dsize_t inStride = sizeof(TSSkinMesh::BatchData::BatchedVertWeight);
__m128 sseMat[3];
sseMat[0] = _mm_loadu_ps(&mat[0]);
sseMat[1] = _mm_loadu_ps(&mat[4]);
sseMat[2] = _mm_loadu_ps(&mat[8]);
// temp registers
__m128 inPos, tempPos;
__m128 inNrm, tempNrm;
__m128 temp0, temp1, temp2, temp3;
// pre-populate cache
const TSSkinMesh::BatchData::BatchedVertWeight &firstElem = batch[0];
for(int i = 0; i < 8; i++)
{
_mm_prefetch(reinterpret_cast<const char *>(iPtr + inStride * i), _MM_HINT_T0);
_mm_prefetch(reinterpret_cast<const char *>(outPtr + outStride * (i + firstElem.vidx)), _MM_HINT_T0);
}
for(int i = 0; i < count; i++)
{
const TSSkinMesh::BatchData::BatchedVertWeight &inElem = batch[i];
TSMesh::__TSMeshVertexBase *outElem = reinterpret_cast<TSMesh::__TSMeshVertexBase *>(outPtr + inElem.vidx * outStride);
// process x (hiding the prefetches in the delays)
inPos = _mm_load_ps(inElem.vert);
inNrm = _mm_load_ps(inElem.normal);
// prefetch input
#define INPUT_PREFETCH_LOOKAHEAD 64
const char *prefetchInput = reinterpret_cast<const char *>(batch) + inStride * (i + INPUT_PREFETCH_LOOKAHEAD);
_mm_prefetch(prefetchInput, _MM_HINT_T0);
// prefetch ouput with half the lookahead distance of the input
#define OUTPUT_PREFETCH_LOOKAHEAD (INPUT_PREFETCH_LOOKAHEAD >> 1)
const char *outPrefetch = reinterpret_cast<const char*>(outPtr) + outStride * (inElem.vidx + OUTPUT_PREFETCH_LOOKAHEAD);
_mm_prefetch(outPrefetch, _MM_HINT_T0);
// Multiply position
tempPos = _mm_dp_ps(inPos, sseMat[0], 0xF1);
temp0 = _mm_dp_ps(inPos, sseMat[1], 0xF2);
temp1 = _mm_dp_ps(inPos, sseMat[2], 0xF4);
temp0 = _mm_or_ps(temp0, temp1);
tempPos = _mm_or_ps(tempPos, temp0);
// Multiply normal
tempNrm = _mm_dp_ps(inNrm, sseMat[0], 0x71);
temp2 = _mm_dp_ps(inNrm, sseMat[1], 0x72);
temp3 = _mm_dp_ps(inNrm, sseMat[2], 0x74);
temp2 = _mm_or_ps(temp2, temp3);
tempNrm = _mm_or_ps(tempNrm, temp2);
// Load bone weight and multiply
temp3 = _mm_shuffle_ps(inPos, inPos, _MM_SHUFFLE(3, 3, 3, 3));
tempPos = _mm_mul_ps(tempPos, temp3);
tempNrm = _mm_mul_ps(tempNrm, temp3);
inPos = _mm_load_ps(outElem->_vert); //< load position for accumulation
inNrm = _mm_load_ps(outElem->_normal); //< load normal for accumulation
// accumulate with previous values
tempNrm = _mm_add_ps(tempNrm, inNrm);
tempPos = _mm_add_ps(tempPos, inPos);
_mm_store_ps(outElem->_vert, tempPos); //< output position
_mm_store_ps(outElem->_normal, tempNrm); //< output normal
}
}
#endif // TORQUE_CPU_X86

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_

View file

@ -0,0 +1,67 @@
//-----------------------------------------------------------------------------
// 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/instancingMatHook.h"
#include "platform/profiler.h"
#include "materials/materialFeatureTypes.h"
#include "materials/matInstance.h"
const MatInstanceHookType InstancingMaterialHook::Type( "Instancing" );
InstancingMaterialHook::InstancingMaterialHook() :
mMatInst( NULL )
{
}
InstancingMaterialHook::~InstancingMaterialHook()
{
SAFE_DELETE( mMatInst );
}
BaseMatInstance* InstancingMaterialHook::getInstancingMat( BaseMatInstance *matInst )
{
PROFILE_SCOPE( InstancingMaterialHook_GetInstancingMat );
if ( matInst == NULL )
return NULL;
InstancingMaterialHook *hook = matInst->getHook<InstancingMaterialHook>();
if ( hook == NULL )
{
hook = new InstancingMaterialHook();
matInst->addHook( hook );
BaseMatInstance *instMat = matInst->getMaterial()->createMatInstance();
FeatureSet features( matInst->getRequestedFeatures() );
features.addFeature( MFT_UseInstancing );
if ( !instMat->init( features, matInst->getVertexFormat() ) )
SAFE_DELETE( instMat );
hook->mMatInst = instMat;
}
return hook->mMatInst;
}

View file

@ -0,0 +1,57 @@
//-----------------------------------------------------------------------------
// 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 _INSTANCINGMATHOOK_H_
#define _INSTANCINGMATHOOK_H_
#ifndef _MATINSTANCEHOOK_H_
#include "materials/matInstanceHook.h"
#endif
class BaseMatInstance;
class InstancingMaterialHook : public MatInstanceHook
{
public:
/// The material hook type.
static const MatInstanceHookType Type;
InstancingMaterialHook();
virtual ~InstancingMaterialHook();
// MatInstanceHook
virtual const MatInstanceHookType& getType() const { return Type; }
/// Returns the instancing material instance or the input material
/// instance if one could not be created.
static BaseMatInstance* getInstancingMat( BaseMatInstance *matInst );
protected:
/// The instancing material.
BaseMatInstance *mMatInst;
};
#endif // _INSTANCINGMATHOOK_H_

View file

@ -0,0 +1,40 @@
//-----------------------------------------------------------------------------
// 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 _APPMATERIAL_H_
#define _APPMATERIAL_H_
struct AppMaterial
{
U32 flags;
F32 reflectance;
AppMaterial() : flags(0), reflectance(1.0f) { }
virtual ~AppMaterial() {}
virtual String getName() const { return "unnamed"; }
virtual U32 getFlags() { return flags; }
virtual F32 getReflectance() { return reflectance; }
};
#endif // _APPMATERIAL_H_

View file

@ -0,0 +1,178 @@
//-----------------------------------------------------------------------------
// 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 "ts/loader/appMesh.h"
#include "ts/loader/tsShapeLoader.h"
Vector<AppMaterial*> AppMesh::appMaterials;
AppMesh::AppMesh()
: flags(0), numFrames(0), numMatFrames(0), vertsPerFrame(0)
{
}
AppMesh::~AppMesh()
{
}
void AppMesh::computeBounds(Box3F& bounds)
{
bounds = Box3F::Invalid;
if ( isSkin() )
{
// Need to skin the mesh before we can compute the bounds
// Setup bone transforms
Vector<MatrixF> boneTransforms;
boneTransforms.setSize( nodeIndex.size() );
for (S32 iBone = 0; iBone < boneTransforms.size(); iBone++)
{
MatrixF nodeMat = bones[iBone]->getNodeTransform( TSShapeLoader::DefaultTime );
TSShapeLoader::zapScale(nodeMat);
boneTransforms[iBone].mul( nodeMat, initialTransforms[iBone] );
}
// Multiply verts by weighted bone transforms
for (S32 iVert = 0; iVert < initialVerts.size(); iVert++)
points[iVert].set( Point3F::Zero );
for (S32 iWeight = 0; iWeight < vertexIndex.size(); iWeight++)
{
const S32& vertIndex = vertexIndex[iWeight];
const MatrixF& deltaTransform = boneTransforms[ boneIndex[iWeight] ];
Point3F v;
deltaTransform.mulP( initialVerts[vertIndex], &v );
v *= weight[iWeight];
points[vertIndex] += v;
}
// compute bounds for the skinned mesh
for (S32 iVert = 0; iVert < initialVerts.size(); iVert++)
bounds.extend( points[iVert] );
}
else
{
MatrixF transform = getMeshTransform(TSShapeLoader::DefaultTime);
TSShapeLoader::zapScale(transform);
for (S32 iVert = 0; iVert < points.size(); iVert++)
{
Point3F p;
transform.mulP(points[iVert], &p);
bounds.extend(p);
}
}
}
void AppMesh::computeNormals()
{
// Clear normals
normals.setSize( points.size() );
for (S32 iNorm = 0; iNorm < normals.size(); iNorm++)
normals[iNorm] = Point3F::Zero;
// Sum triangle normals for each vertex
for (S32 iPrim = 0; iPrim < primitives.size(); iPrim++)
{
const TSDrawPrimitive& prim = primitives[iPrim];
for (S32 iInd = 0; iInd < prim.numElements; iInd += 3)
{
// Compute the normal for this triangle
S32 idx0 = indices[prim.start + iInd + 0];
S32 idx1 = indices[prim.start + iInd + 1];
S32 idx2 = indices[prim.start + iInd + 2];
const Point3F& v0 = points[idx0];
const Point3F& v1 = points[idx1];
const Point3F& v2 = points[idx2];
Point3F n;
mCross(v2 - v0, v1 - v0, &n);
n.normalize(); // remove this to use 'weighted' normals (large triangles will have more effect)
normals[idx0] += n;
normals[idx1] += n;
normals[idx2] += n;
}
}
// Normalize the vertex normals (this takes care of averaging the triangle normals)
for (S32 iNorm = 0; iNorm < normals.size(); iNorm++)
normals[iNorm].normalize();
}
TSMesh* AppMesh::constructTSMesh()
{
TSMesh* tsmesh;
if (isSkin())
{
TSSkinMesh* tsskin = new TSSkinMesh();
tsmesh = tsskin;
// Copy skin elements
tsskin->weight = weight;
tsskin->boneIndex = boneIndex;
tsskin->vertexIndex = vertexIndex;
tsskin->batchData.nodeIndex = nodeIndex;
tsskin->batchData.initialTransforms = initialTransforms;
tsskin->batchData.initialVerts = initialVerts;
tsskin->batchData.initialNorms = initialNorms;
}
else
{
tsmesh = new TSMesh();
}
// Copy mesh elements
tsmesh->verts = points;
tsmesh->norms = normals;
tsmesh->tverts = uvs;
tsmesh->primitives = primitives;
tsmesh->indices = indices;
tsmesh->colors = colors;
tsmesh->tverts2 = uv2s;
// Finish initializing the shape
tsmesh->setFlags(flags);
tsmesh->computeBounds();
tsmesh->numFrames = numFrames;
tsmesh->numMatFrames = numMatFrames;
tsmesh->vertsPerFrame = vertsPerFrame;
tsmesh->createTangents(tsmesh->verts, tsmesh->norms);
tsmesh->encodedNorms.set(NULL,0);
return tsmesh;
}
bool AppMesh::isBillboard()
{
return !dStrnicmp(getName(),"BB::",4) || !dStrnicmp(getName(),"BB_",3) || isBillboardZAxis();
}
bool AppMesh::isBillboardZAxis()
{
return !dStrnicmp(getName(),"BBZ::",5) || !dStrnicmp(getName(),"BBZ_",4);
}

View file

@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// 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 _APPMESH_H_
#define _APPMESH_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _APPMATERIAL_H_
#include "ts/loader/appMaterial.h"
#endif
#ifndef _APPSEQUENCE_H_
#include "ts/loader/appSequence.h"
#endif
class AppNode;
class AppMesh
{
public:
// Mesh and skin elements
Vector<Point3F> points;
Vector<Point3F> normals;
Vector<Point2F> uvs;
Vector<Point2F> uv2s;
Vector<ColorI> colors;
Vector<TSDrawPrimitive> primitives;
Vector<U32> indices;
// Skin elements
Vector<F32> weight;
Vector<S32> boneIndex;
Vector<S32> vertexIndex;
Vector<S32> nodeIndex;
Vector<MatrixF> initialTransforms;
Vector<Point3F> initialVerts;
Vector<Point3F> initialNorms;
U32 flags;
U32 vertsPerFrame;
S32 numFrames;
S32 numMatFrames;
// Loader elements (can be discarded after loading)
S32 detailSize;
MatrixF objectOffset;
Vector<AppNode*> bones;
static Vector<AppMaterial*> appMaterials;
public:
AppMesh();
virtual ~AppMesh();
void computeBounds(Box3F& bounds);
void computeNormals();
// Create a TSMesh object
TSMesh* constructTSMesh();
virtual const char * getName(bool allowFixed=true) = 0;
virtual MatrixF getMeshTransform(F32 time) = 0;
virtual F32 getVisValue(F32 time) = 0;
virtual bool getFloat(const char* propName, F32& defaultVal) = 0;
virtual bool getInt(const char* propName, S32& defaultVal) = 0;
virtual bool getBool(const char* propName, bool& defaultVal) = 0;
virtual bool animatesVis(const AppSequence* appSeq) { return false; }
virtual bool animatesMatFrame(const AppSequence* appSeq) { return false; }
virtual bool animatesFrame(const AppSequence* appSeq) { return false; }
virtual bool isBillboard();
virtual bool isBillboardZAxis();
virtual bool isSkin() { return false; }
virtual void lookupSkinData() = 0;
virtual void lockMesh(F32 t, const MatrixF& objectOffset) { }
};
#endif // _APPMESH_H_

View file

@ -0,0 +1,104 @@
//-----------------------------------------------------------------------------
// 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 "ts/loader/appNode.h"
AppNode::AppNode()
{
mName = NULL;
mParentName = NULL;
}
AppNode::~AppNode()
{
dFree( mName );
dFree( mParentName );
// delete children and meshes
for (S32 i = 0; i < mChildNodes.size(); i++)
delete mChildNodes[i];
for (S32 i = 0; i < mMeshes.size(); i++)
delete mMeshes[i];
}
S32 AppNode::getNumMesh()
{
if (mMeshes.size() == 0)
buildMeshList();
return mMeshes.size();
}
AppMesh* AppNode::getMesh(S32 idx)
{
return (idx < getNumMesh() && idx >= 0) ? mMeshes[idx] : NULL;
}
S32 AppNode::getNumChildNodes()
{
if (mChildNodes.size() == 0)
buildChildList();
return mChildNodes.size();
}
AppNode * AppNode::getChildNode(S32 idx)
{
return (idx < getNumChildNodes() && idx >= 0) ? mChildNodes[idx] : NULL;
}
bool AppNode::isBillboard()
{
return !dStrnicmp(getName(),"BB::",4) || !dStrnicmp(getName(),"BB_",3) || isBillboardZAxis();
}
bool AppNode::isBillboardZAxis()
{
return !dStrnicmp(getName(),"BBZ::",5) || !dStrnicmp(getName(),"BBZ_",4);
}
bool AppNode::isDummy()
{
// naming convention should work well enough...
// ...but can override this method if one wants more
return !dStrnicmp(getName(), "dummy", 5);
}
bool AppNode::isBounds()
{
// naming convention should work well enough...
// ...but can override this method if one wants more
return !dStricmp(getName(), "bounds");
}
bool AppNode::isSequence()
{
// naming convention should work well enough...
// ...but can override this method if one wants more
return !dStrnicmp(getName(), "Sequence", 8);
}
bool AppNode::isRoot()
{
// we assume root node isn't added, so this is never true
// but allow for possibility (by overriding this method)
// so that isParentRoot still works.
return false;
}

View file

@ -0,0 +1,88 @@
//-----------------------------------------------------------------------------
// 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 _APPNODE_H_
#define _APPNODE_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _APPMESH_H_
#include "ts/loader/appMesh.h"
#endif
class AppNode
{
friend class TSShapeLoader;
// add attached meshes and child nodes to app node
// the reason these are tracked by AppNode is that
// AppNode is responsible for deleting all it's children
// and attached meshes.
virtual void buildMeshList() = 0;
virtual void buildChildList() = 0;
protected:
S32 mParentIndex;
Vector<AppMesh*> mMeshes;
Vector<AppNode*> mChildNodes;
char* mName;
char* mParentName;
public:
AppNode();
virtual ~AppNode();
S32 getNumMesh();
AppMesh* getMesh(S32 idx);
S32 getNumChildNodes();
AppNode* getChildNode(S32 idx);
virtual MatrixF getNodeTransform(F32 time) = 0;
virtual bool isEqual(AppNode* node) = 0;
virtual bool animatesTransform(const AppSequence* appSeq) = 0;
virtual const char* getName() = 0;
virtual const char* getParentName() = 0;
virtual bool getFloat(const char* propName, F32& defaultVal) = 0;
virtual bool getInt(const char* propName, S32& defaultVal) = 0;
virtual bool getBool(const char* propName, bool& defaultVal) = 0;
virtual bool isBillboard();
virtual bool isBillboardZAxis();
virtual bool isParentRoot() = 0;
virtual bool isDummy();
virtual bool isBounds();
virtual bool isSequence();
virtual bool isRoot();
};
#endif // _APPNODE_H_

View file

@ -0,0 +1,60 @@
//-----------------------------------------------------------------------------
// 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 _APPSEQUENCE_H_
#define _APPSEQUENCE_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TSSHAPE_H_
#include "ts/tsShape.h"
#endif
class AppSequence
{
public:
S32 fps;
public:
AppSequence() { }
virtual ~AppSequence() { }
virtual void setActive(bool active) { }
virtual S32 getNumTriggers() const { return 0; }
virtual void getTrigger(S32 index, TSShape::Trigger& trigger) const { trigger.state = 0;}
virtual const char* getName() const { return "ambient"; }
virtual F32 getStart() const { return 0.0f; }
virtual F32 getEnd() const { return 0.0f; }
virtual U32 getFlags() const { return 0; }
virtual F32 getPriority() const { return 5; }
virtual F32 getBlendRefTime() const { return 0.0f; }
};
#endif // _APPSEQUENCE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,183 @@
//-----------------------------------------------------------------------------
// 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 _TSSHAPE_LOADER_H_
#define _TSSHAPE_LOADER_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TSSHAPE_H_
#include "ts/tsShape.h"
#endif
#ifndef _APPNODE_H_
#include "ts/loader/appNode.h"
#endif
#ifndef _APPMESH_H_
#include "ts/loader/appMesh.h"
#endif
#ifndef _APPSEQUENCE_H_
#include "ts/loader/appSequence.h"
#endif
class TSShapeLoader
{
public:
enum eLoadPhases
{
Load_ReadFile = 0,
Load_ParseFile,
Load_ExternalRefs,
Load_EnumerateScene,
Load_GenerateSubshapes,
Load_GenerateObjects,
Load_GenerateDefaultStates,
Load_GenerateSkins,
Load_GenerateMaterials,
Load_GenerateSequences,
Load_InitShape,
NumLoadPhases,
Load_Complete = NumLoadPhases
};
static void updateProgress(int major, const char* msg, int numMinor=0, int minor=0);
protected:
struct Subshape
{
Vector<AppNode*> branches; ///< Shape branches
Vector<AppMesh*> objMeshes; ///< Object meshes for this subshape
Vector<S32> objNodes; ///< AppNode indices with objects attached
~Subshape()
{
// Delete children
for (S32 i = 0; i < branches.size(); i++)
delete branches[i];
}
};
public:
static const F32 DefaultTime;
static const double MinFrameRate;
static const double MaxFrameRate;
static const double AppGroundFrameRate;
protected:
// Variables used during loading that must be held until the shape is deleted
TSShape* shape;
Vector<AppMesh*> appMeshes;
// Variables used during loading, but that can be discarded afterwards
static Torque::Path shapePath;
AppNode* boundsNode;
Vector<AppNode*> appNodes; ///< Nodes in the loaded shape
Vector<AppSequence*> appSequences;
Vector<Subshape*> subshapes;
Vector<QuatF*> nodeRotCache;
Vector<Point3F*> nodeTransCache;
Vector<QuatF*> nodeScaleRotCache;
Vector<Point3F*> nodeScaleCache;
Point3F shapeOffset; ///< Offset used to translate the shape origin
//--------------------------------------------------------------------------
// Collect the nodes, objects and sequences for the scene
virtual void enumerateScene() = 0;
bool processNode(AppNode* node);
virtual bool ignoreNode(const String& name) { return false; }
virtual bool ignoreMesh(const String& name) { return false; }
void addSkin(AppMesh* mesh);
void addDetailMesh(AppMesh* mesh);
void addSubshape(AppNode* node);
void addObject(AppMesh* mesh, S32 nodeIndex, S32 subShapeNum);
// Node transform methods
MatrixF getLocalNodeMatrix(AppNode* node, F32 t);
void generateNodeTransform(AppNode* node, F32 t, bool blend, F32 referenceTime,
QuatF& rot, Point3F& trans, QuatF& srot, Point3F& scale);
virtual void computeBounds(Box3F& bounds);
// Create objects, materials and sequences
void recurseSubshape(AppNode* appNode, S32 parentIndex, bool recurseChildren);
void generateSubshapes();
void generateObjects();
void generateSkins();
void generateDefaultStates();
void generateObjectState(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame);
void generateFrame(TSShape::Object& obj, F32 t, bool addFrame, bool addMatFrame);
void generateMaterialList();
void generateSequences();
// Determine what is actually animated in the sequence
void setNodeMembership(TSShape::Sequence& seq, const AppSequence* appSeq);
void setRotationMembership(TSShape::Sequence& seq);
void setTranslationMembership(TSShape::Sequence& seq);
void setScaleMembership(TSShape::Sequence& seq);
void setObjectMembership(TSShape::Sequence& seq, const AppSequence* appSeq);
// Manage a cache of all node transform elements for the sequence
void clearNodeTransformCache();
void fillNodeTransformCache(TSShape::Sequence& seq, const AppSequence* appSeq);
// Add node transform elements
void addNodeRotation(QuatF& rot, bool defaultVal);
void addNodeTranslation(Point3F& trans, bool defaultVal);
void addNodeUniformScale(F32 scale);
void addNodeAlignedScale(Point3F& scale);
void addNodeArbitraryScale(QuatF& qrot, Point3F& scale);
// Generate animation data
void generateNodeAnimation(TSShape::Sequence& seq);
void generateObjectAnimation(TSShape::Sequence& seq, const AppSequence* appSeq);
void generateGroundAnimation(TSShape::Sequence& seq, const AppSequence* appSeq);
void generateFrameTriggers(TSShape::Sequence& seq, const AppSequence* appSeq);
// Shape construction
void sortDetails();
void install();
public:
TSShapeLoader() : boundsNode(0) { }
virtual ~TSShapeLoader();
static const Torque::Path& getShapePath() { return shapePath; }
static void zapScale(MatrixF& mat);
TSShape* generateShape(const Torque::Path& path);
};
#endif // _TSSHAPE_LOADER_H_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,107 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsDecal.h"
#include "math/mMath.h"
#include "math/mathIO.h"
#include "ts/tsShapeInstance.h"
#include "core/frameAllocator.h"
//-----------------------------------------------------
// TSDecalMesh assembly/dissembly methods
// used for transfer to/from memory buffers
//-----------------------------------------------------
#define tsalloc TSShape::smTSAlloc
void TSDecalMesh::assemble(bool)
{
if (TSShape::smReadVersion<20)
{
// read empty mesh...decals used to be derived from meshes
tsalloc.checkGuard();
tsalloc.getPointer32(15);
}
S32 sz = tsalloc.get32();
S32 * ptr32 = tsalloc.copyToShape32(0); // get current shape address w/o doing anything
for (S32 i=0; i<sz; i++)
{
tsalloc.getPointer16(2);
tsalloc.getPointer32(1);
}
tsalloc.align32();
primitives.set(ptr32,sz);
sz = tsalloc.get32();
S16 * ptr16 = tsalloc.getPointer16(sz);
tsalloc.align32();
indices.set(ptr16,sz);
if (TSShape::smReadVersion<20)
{
// read more empty mesh stuff...decals used to be derived from meshes
tsalloc.getPointer32(3);
tsalloc.checkGuard();
}
sz = tsalloc.get32();
ptr32 = tsalloc.getPointer32(sz);
startPrimitive.set(ptr32,sz);
if (TSShape::smReadVersion>=19)
{
ptr32 = tsalloc.getPointer32(sz*4);
texgenS.set(ptr32,startPrimitive.size());
ptr32 = tsalloc.getPointer32(sz*4);
texgenT.set(ptr32,startPrimitive.size());
}
else
{
texgenS.set(NULL,0);
texgenT.set(NULL,0);
}
materialIndex = tsalloc.get32();
tsalloc.checkGuard();
}
void TSDecalMesh::disassemble()
{
tsalloc.set32(primitives.size());
tsalloc.copyToBuffer32((S32*)primitives.address(),primitives.size());
tsalloc.set32(indices.size());
tsalloc.copyToBuffer32((S32*)indices.address(),indices.size());
tsalloc.set32(startPrimitive.size());
tsalloc.copyToBuffer32((S32*)startPrimitive.address(),startPrimitive.size());
tsalloc.copyToBuffer32((S32*)texgenS.address(),texgenS.size()*4);
tsalloc.copyToBuffer32((S32*)texgenT.address(),texgenT.size()*4);
tsalloc.set32(materialIndex);
tsalloc.setGuard();
}

View file

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// 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 _TSDECAL_H_
#define _TSDECAL_H_
#ifndef _TSMESH_H_
#include "ts/tsMesh.h"
#endif
/// Decals! The lovely detailing thingies, e.g. bullet hole marks.
/// DEPRECATED: This class is here for compatibility with old files only.
/// Performs no actual rendering.
class TSDecalMesh
{
public:
/// The mesh that we are decaling
TSMesh * targetMesh;
/// @name Topology
/// @{
Vector<TSDrawPrimitive> primitives;
Vector<U16> indices;
/// @}
/// @name Render Data
/// indexed by decal frame...
/// @{
Vector<S32> startPrimitive;
Vector<Point4F> texgenS;
Vector<Point4F> texgenT;
/// @}
/// We only allow 1 material per decal...
S32 materialIndex;
/// DEPRECATED
// void render(S32 frame, S32 decalFrame, TSMaterialList *);
void disassemble();
void assemble(bool skip);
};
#endif

231
Engine/source/ts/tsDump.cpp Normal file
View file

@ -0,0 +1,231 @@
//-----------------------------------------------------------------------------
// 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/tsShapeInstance.h"
#include "ts/tsMaterialList.h"
#include "core/strings/stringFunctions.h"
//-------------------------------------------------------------------------------------
// Dump shape structure:
//-------------------------------------------------------------------------------------
#define dumpLine(buffer) {str = buffer; stream.write((int)dStrlen(str),str);}
void TSShapeInstance::dumpNode(Stream & stream ,S32 level, S32 nodeIndex, Vector<S32> & detailSizes)
{
if (nodeIndex < 0)
return;
// limit level to prevent overflow
if (level > 160)
level = 160;
S32 i;
const char * str;
char space[512];
for (i = 0; i < level*3; i++)
space[i] = ' ';
space[level*3] = '\0';
const char *nodeName = "";
const TSShape::Node & node = mShape->nodes[nodeIndex];
if (node.nameIndex != -1)
nodeName = mShape->getName(node.nameIndex);
dumpLine(avar("%s%s", space, nodeName));
// find all the objects that hang off this node...
Vector<ObjectInstance*> objectList;
for (i=0; i<mMeshObjects.size(); i++)
if (mMeshObjects[i].nodeIndex == nodeIndex)
objectList.push_back(&mMeshObjects[i]);
if (objectList.size() == 0)
dumpLine("\r\n");
S32 spaceCount = -1;
for (S32 j=0;j<objectList.size(); j++)
{
// should be a dynamic cast, but MSVC++ has problems with this...
MeshObjectInstance * obj = (MeshObjectInstance *)(objectList[j]);
if (!obj)
continue;
// object name
const char *objectName = "";
if (obj->object->nameIndex!=-1)
objectName = mShape->getName(obj->object->nameIndex);
// more spaces if this is the second object on this node
if (spaceCount>0)
{
char buf[2048];
dMemset(buf,' ',spaceCount);
buf[spaceCount] = '\0';
dumpLine(buf);
}
// dump object name
dumpLine(avar(" --> Object %s with following details: ",objectName));
// dump object detail levels
for (S32 k=0; k<obj->object->numMeshes; k++)
{
S32 f = obj->object->startMeshIndex;
if (mShape->meshes[f+k])
dumpLine(avar(" %i",detailSizes[k]));
}
dumpLine("\r\n");
// how many spaces should we prepend if we have another object on this node
if (spaceCount<0)
spaceCount = (S32)(dStrlen(space) + dStrlen(nodeName));
if(spaceCount > 2000)
spaceCount = 2000;
}
// search for children
for (S32 k=nodeIndex+1; k<mShape->nodes.size(); k++)
{
if (mShape->nodes[k].parentIndex == nodeIndex)
// this is our child
dumpNode(stream, level+1, k, detailSizes);
}
}
void TSShapeInstance::dump(Stream & stream)
{
S32 i,j,ss,od,sz;
const char * name;
const char * str;
dumpLine("\r\nShape Hierarchy:\r\n");
dumpLine("\r\n Details:\r\n");
for (i=0; i<mShape->details.size(); i++)
{
const TSDetail & detail = mShape->details[i];
name = mShape->getName(detail.nameIndex);
ss = detail.subShapeNum;
od = detail.objectDetailNum;
sz = (S32)detail.size;
if (ss >= 0)
{
dumpLine(avar(" %s, Subtree %i, objectDetail %i, size %i\r\n",name,ss,od,sz));
}
else
{
dumpLine(avar(" %s, AutoBillboard, size %i\r\n", name, sz));
}
}
dumpLine("\r\n Subtrees:\r\n");
for (i=0; i<mShape->subShapeFirstNode.size(); i++)
{
S32 a = mShape->subShapeFirstNode[i];
S32 b = a + mShape->subShapeNumNodes[i];
dumpLine(avar(" Subtree %i\r\n",i));
// compute detail sizes for each subshape
Vector<S32> detailSizes;
for (S32 l=0;l<mShape->details.size(); l++)
{
if ((mShape->details[l].subShapeNum==i) || (mShape->details[l].subShapeNum==-1))
detailSizes.push_back((S32)mShape->details[l].size);
}
for (j=a; j<b; j++)
{
const TSNode & node = mShape->nodes[j];
// if the node has a parent, it'll get dumped via the parent
if (node.parentIndex<0)
dumpNode(stream,3,j,detailSizes);
}
}
bool foundSkin = false;
for (i=0; i<mShape->objects.size(); i++)
{
if (mShape->objects[i].nodeIndex<0) // must be a skin
{
if (!foundSkin)
dumpLine("\r\n Skins:\r\n");
foundSkin=true;
const char * skinName = "";
S32 nameIndex = mShape->objects[i].nameIndex;
if (nameIndex>=0)
skinName = mShape->getName(nameIndex);
dumpLine(avar(" Skin %s with following details: ",skinName));
for (S32 num=0; num<mShape->objects[i].numMeshes; num++)
{
if (mShape->meshes[mShape->objects[i].startMeshIndex + num])
dumpLine(avar(" %i",(S32)mShape->details[num].size));
}
dumpLine("\r\n");
}
}
if (foundSkin)
dumpLine("\r\n");
dumpLine("\r\n Sequences:\r\n");
for (i = 0; i < mShape->sequences.size(); i++)
{
const char *name = "(none)";
if (mShape->sequences[i].nameIndex != -1)
name = mShape->getName(mShape->sequences[i].nameIndex);
dumpLine(avar(" %3d: %s%s%s\r\n", i, name,
mShape->sequences[i].isCyclic() ? " (cyclic)" : "",
mShape->sequences[i].isBlend() ? " (blend)" : ""));
}
if (mShape->materialList)
{
TSMaterialList * ml = mShape->materialList;
dumpLine("\r\n Material list:\r\n");
for (i=0; i<(S32)ml->size(); i++)
{
U32 flags = ml->getFlags(i);
const String& name = ml->getMaterialName(i);
dumpLine(avar(
" material #%i: '%s'%s.", i, name.c_str(),
flags & (TSMaterialList::S_Wrap|TSMaterialList::T_Wrap) ? "" : " not tiled")
);
if (flags & TSMaterialList::Translucent)
{
if (flags & TSMaterialList::Additive)
dumpLine(" Additive-translucent.")
else if (flags & TSMaterialList::Subtractive)
dumpLine(" Subtractive-translucent.")
else
dumpLine(" Translucent.")
}
dumpLine("\r\n");
}
}
}

View file

@ -0,0 +1,306 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsIntegerSet.h"
#include "platform/platform.h"
#include "core/stream/stream.h"
#define SETUPTO(upto) ( ((1<<(upto&31))-1)*2+1 ) // careful not to shift more than 31 times
void TSIntegerSet::clearAll(S32 upto)
{
AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::clearAll: out of range");
dMemset(bits,0,(upto>>5)*4);
if (upto&31)
bits[upto>>5] &= ~SETUPTO(upto);
}
void TSIntegerSet::setAll(S32 upto)
{
AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::setAll: out of range");
dMemset(bits,0xFFFFFFFF,(upto>>5)*4);
if (upto&31)
bits[upto>>5] |= SETUPTO(upto);
}
bool TSIntegerSet::testAll(S32 upto) const
{
AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::testAll: out of range");
S32 i;
for (i=0; i<(upto>>5); i++)
if (bits[i])
return true;
if (upto&31)
return (bits[upto>>5] & SETUPTO(upto)) != 0;
return false;
}
S32 TSIntegerSet::count(S32 upto) const
{
AssertFatal(upto<=MAX_TS_SET_SIZE,"TSIntegerSet::count: out of range");
S32 count = 0;
U32 dword = bits[0];
for (S32 i = 0; i < upto; i++)
{
// get next word
if (!(i & 0x1F))
dword = bits[i>>5];
// test bits in word
if (dword & (1 << (i & 0x1F)))
count++;
}
return count;
}
void TSIntegerSet::intersect(const TSIntegerSet & otherSet)
{
for (S32 i=0; i<MAX_TS_SET_DWORDS; i++)
bits[i] &= otherSet.bits[i];
}
void TSIntegerSet::overlap(const TSIntegerSet & otherSet)
{
for (S32 i=0; i<MAX_TS_SET_DWORDS; i++)
bits[i] |= otherSet.bits[i];
}
void TSIntegerSet::difference(const TSIntegerSet & otherSet)
{
for (S32 i=0; i<MAX_TS_SET_DWORDS; i++)
bits[i] = (bits[i] | otherSet.bits[i]) & ~(bits[i] & otherSet.bits[i]);
}
void TSIntegerSet::takeAway(const TSIntegerSet & otherSet)
{
for (S32 i=0; i<MAX_TS_SET_DWORDS; i++)
bits[i] &= ~otherSet.bits[i];
}
S32 TSIntegerSet::start() const
{
for (S32 i=0; i<MAX_TS_SET_DWORDS; i++)
{
// search for set bit one dword at a time
U32 dword = bits[i];
if (dword!=0)
{
// got dword, now search one byte at a time
S32 j = 0;
U32 mask = 0xFF;
do
{
if (dword&mask)
{
// got byte, now search one bit at a time
U32 bit = mask & ~(mask<<1); // grabs the smallest bit
do
{
if (dword&bit)
return (i<<5)+j;
j++;
bit <<= 1;
} while (1);
}
mask <<= 8;
j += 8;
} while (1);
}
}
return MAX_TS_SET_SIZE;
}
S32 TSIntegerSet::end() const
{
for (S32 i=MAX_TS_SET_DWORDS-1; i>=0; i--)
{
// search for set bit one dword at a time
U32 dword = bits[i];
if (bits[i])
{
// got dword, now search one byte at a time
S32 j = 31;
U32 mask = 0xFF000000;
do
{
if (dword&mask)
{
// got byte, now one bit at a time
U32 bit = mask & ~(mask>>1); // grabs the highest bit
do
{
if (dword&bit)
return (i<<5)+j+1;
j--;
bit >>= 1;
} while (1);
}
mask >>= 8;
j -= 8;
} while (1);
}
}
return 0;
}
void TSIntegerSet::next(S32 & i) const
{
i++;
U32 idx = i>>5;
U32 bit = 1 << (i&31);
U32 dword = bits[idx] & ~(bit-1);
while (dword==0)
{
i = (i+32) & ~31;
if (i>=MAX_TS_SET_SIZE)
return;
dword=bits[++idx];
bit = 1;
}
dword = bits[idx];
while ( (bit & dword) == 0)
{
bit <<= 1;
i++;
}
}
/* Or would one byte at a time be better...
void TSIntegerSet::next(S32 & i)
{
U32 idx = i>>3;
U8 bit = 1 << (i&7);
U8 byte = ((U8*)bits)[idx] & ~(bit*2-1);
while (byte==0)
{
i = (i+8) & ~7;
if (i>=MAX_TS_SET_SIZE)
return;
byte=((U8*)bits)[++idx];
bit = 1;
}
byte = ((U8*)bits)[idx];
while (bit & byte == 0)
{
bit <<= 1;
i++;
}
}
*/
void TSIntegerSet::copy(const TSIntegerSet & otherSet)
{
dMemcpy(bits,otherSet.bits,MAX_TS_SET_DWORDS*4);
}
void TSIntegerSet::insert(S32 index, bool value)
{
AssertFatal(index<MAX_TS_SET_SIZE,"TSIntegerSet::insert: out of range");
// shift bits in words after the insertion point
U32 endWord = (end() >> 5) + 1;
if (endWord >= MAX_TS_SET_DWORDS)
endWord = MAX_TS_SET_DWORDS-1;
for (S32 i = endWord; i > (index >> 5); i--)
{
bits[i] = bits[i] << 1;
if (bits[i-1] & 0x80000000)
bits[i] |= 0x1;
}
// shift to create space in target word
U32 lowMask = (1 << (index & 0x1f)) - 1; // bits below the insert point
U32 highMask = ~(lowMask | (1 << (index & 0x1f))); // bits above the insert point
S32 word = index >> 5;
bits[word] = ((bits[word] << 1) & highMask) | (bits[word] & lowMask);
// insert new value
if (value)
set(index);
}
void TSIntegerSet::erase(S32 index)
{
AssertFatal(index<MAX_TS_SET_SIZE,"TSIntegerSet::erase: out of range");
// shift to erase bit in target word
S32 word = index >> 5;
U32 lowMask = (1 << (index & 0x1f)) - 1; // bits below the erase point
bits[word] = ((bits[word] >> 1) & ~lowMask) | (bits[word] & lowMask);
// shift bits in words after the erase point
U32 endWord = (end() >> 5) + 1;
if (endWord >= MAX_TS_SET_DWORDS)
endWord = MAX_TS_SET_DWORDS-1;
for (S32 i = (index >> 5) + 1; i <= endWord; i++)
{
if (bits[i] & 0x1)
bits[i-1] |= 0x80000000;
bits[i] = bits[i] >> 1;
}
}
TSIntegerSet::TSIntegerSet()
{
clearAll();
}
TSIntegerSet::TSIntegerSet(const TSIntegerSet & otherSet)
{
copy(otherSet);
}
void TSIntegerSet::read(Stream * s)
{
clearAll();
S32 numInts;
s->read(&numInts); // don't care about this
S32 sz;
s->read(&sz);
AssertFatal(sz<=MAX_TS_SET_DWORDS,"TSIntegerSet:: set too large...increase max set size and re-compile");
for (S32 i=0; i<sz; i++) // now mirrors the write code...
s->read(&(bits[i]));
}
void TSIntegerSet::write(Stream * s) const
{
s->write((S32)0); // don't do this anymore, keep in to avoid versioning
S32 i,sz=0;
for (i=0; i<MAX_TS_SET_DWORDS; i++)
if (bits[i]!=0)
sz=i+1;
s->write(sz);
for (i=0; i<sz; i++)
s->write(bits[i]);
}

View file

@ -0,0 +1,119 @@
//-----------------------------------------------------------------------------
// 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 _TSINTEGERSET_H_
#define _TSINTEGERSET_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#if defined(TORQUE_MAX_LIB)
#define MAX_TS_SET_DWORDS 32
#else
#define MAX_TS_SET_DWORDS 64
#endif
#define MAX_TS_SET_SIZE (32*MAX_TS_SET_DWORDS)
class Stream;
/// The standard mathmatical set, where there are no duplicates. However,
/// this set uses bits instead of numbers.
class TSIntegerSet
{
/// The bits!
U32 bits[MAX_TS_SET_DWORDS];
public:
/// Sets this bit to false
void clear(S32 index);
/// Set this bit to true
void set(S32 index);
/// Is this bit true?
bool test(S32 index) const;
/// Sets all bits to false
void clearAll(S32 upto = MAX_TS_SET_SIZE);
/// Sets all bits to true
void setAll(S32 upto = MAX_TS_SET_SIZE);
/// Tests all bits for true
bool testAll(S32 upto = MAX_TS_SET_SIZE) const;
/// Counts set bits
S32 count(S32 upto = MAX_TS_SET_SIZE) const;
/// intersection (a & b)
void intersect(const TSIntegerSet&);
/// union (a | b)
void overlap(const TSIntegerSet&);
/// xor (a | b) & ( !(a & b) )
void difference(const TSIntegerSet&);
/// subtraction (a - b)
void takeAway(const TSIntegerSet&);
/// copy one integer set into another
void copy(const TSIntegerSet&);
void insert(S32 index, bool value);
void erase(S32 index);
void operator=(const TSIntegerSet& otherSet) { copy(otherSet); }
S32 start() const;
S32 end() const;
void next(S32 & i) const;
void read(Stream *);
void write(Stream *) const;
TSIntegerSet();
TSIntegerSet(const TSIntegerSet&);
};
inline void TSIntegerSet::clear(S32 index)
{
AssertFatal(index>=0 && index<MAX_TS_SET_SIZE,"TS::IntegerSet::clear");
bits[index>>5] &= ~(1 << (index & 31));
}
inline void TSIntegerSet::set(S32 index)
{
AssertFatal(index>=0 && index<MAX_TS_SET_SIZE,"TS::IntegerSet::set");
bits[index>>5] |= 1 << (index & 31);
}
inline bool TSIntegerSet::test(S32 index) const
{
AssertFatal(index>=0 && index<MAX_TS_SET_SIZE,"TS::IntegerSet::test");
return ((bits[index>>5] & (1 << (index & 31)))!=0);
}
#endif

View file

@ -0,0 +1,553 @@
//-----------------------------------------------------------------------------
// 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/tsLastDetail.h"
#include "renderInstance/renderPassManager.h"
#include "ts/tsShapeInstance.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "lighting/lightInfo.h"
#include "renderInstance/renderImposterMgr.h"
#include "gfx/gfxTransformSaver.h"
#include "gfx/bitmap/ddsFile.h"
#include "gfx/bitmap/ddsUtils.h"
#include "gfx/gfxTextureManager.h"
#include "math/mRandom.h"
#include "core/stream/fileStream.h"
#include "util/imposterCapture.h"
#include "materials/materialManager.h"
#include "materials/materialFeatureTypes.h"
#include "console/consoleTypes.h"
GFXImplementVertexFormat( ImposterState )
{
addElement( "POSITION", GFXDeclType_Float4 );
addElement( "ImposterParams", GFXDeclType_Float2, 0 );
addElement( "ImposterUpVec", GFXDeclType_Float3, 1 );
addElement( "ImposterRightVec", GFXDeclType_Float3, 2 );
};
Vector<TSLastDetail*> TSLastDetail::smLastDetails;
bool TSLastDetail::smCanShadow = true;
AFTER_MODULE_INIT( Sim )
{
Con::addVariable( "$pref::imposter::canShadow", TypeBool, &TSLastDetail::smCanShadow,
"User preference which toggles shadows from imposters. Defaults to true.\n"
"@ingroup Rendering\n" );
}
TSLastDetail::TSLastDetail( TSShape *shape,
const String &cachePath,
U32 numEquatorSteps,
U32 numPolarSteps,
F32 polarAngle,
bool includePoles,
S32 dl, S32 dim )
{
mNumEquatorSteps = getMax( numEquatorSteps, (U32)1 );
mNumPolarSteps = numPolarSteps;
mPolarAngle = polarAngle;
mIncludePoles = includePoles;
mShape = shape;
mDl = dl;
mDim = getMax( dim, (S32)32 );
mRadius = mShape->radius;
mCenter = mShape->center;
mCachePath = cachePath;
mMaterial = NULL;
mMatInstance = NULL;
// Store this in the static list.
smLastDetails.push_back( this );
}
TSLastDetail::~TSLastDetail()
{
SAFE_DELETE( mMatInstance );
if ( mMaterial )
mMaterial->deleteObject();
// Remove ourselves from the list.
Vector<TSLastDetail*>::iterator iter = find( smLastDetails.begin(), smLastDetails.end(), this );
smLastDetails.erase( iter );
}
void TSLastDetail::render( const TSRenderState &rdata, F32 alpha )
{
// Early out if we have nothing to render.
if ( alpha < 0.01f ||
!mMatInstance ||
mMaterial->mImposterUVs.size() == 0 )
return;
const MatrixF &mat = GFX->getWorldMatrix();
// Post a render instance for this imposter... the special
// imposter render manager will do the magic!
RenderPassManager *renderPass = rdata.getSceneState()->getRenderPass();
ImposterRenderInst *ri = renderPass->allocInst<ImposterRenderInst>();
ri->mat = rdata.getSceneState()->getOverrideMaterial( mMatInstance );
ri->state.alpha = alpha;
// Store the up and right vectors of the rotation
// and we'll generate the up vector in the shader.
//
// This is faster than building a quat on the
// CPU and then rebuilding the matrix on the GPU.
//
// NOTE: These vector include scale.
//
mat.getColumn( 2, &ri->state.upVec );
mat.getColumn( 0, &ri->state.rightVec );
// We send the unscaled size and the vertex shader
// will use the orientation vectors above to scale it.
ri->state.halfSize = mRadius;
// We use the center of the object bounds for
// the center of the billboard quad.
mat.mulP( mCenter, &ri->state.center );
// We sort by the imposter type first so that RIT_Imposter and s
// RIT_ImposterBatches do not get mixed together.
//
// We then sort by material.
//
ri->defaultKey = 1;
ri->defaultKey2 = ri->mat->getStateHint();
renderPass->addInst( ri );
}
void TSLastDetail::update( bool forceUpdate )
{
// This should never be called on a dedicated server or
// anywhere else where we don't have a GFX device!
AssertFatal( GFXDevice::devicePresent(), "TSLastDetail::update() - Cannot update without a GFX device!" );
// Clear the materialfirst.
SAFE_DELETE( mMatInstance );
if ( mMaterial )
{
mMaterial->deleteObject();
mMaterial = NULL;
}
// Make sure imposter textures have been flushed (and not just queued for deletion)
TEXMGR->cleanupCache();
// Get the real path to the source shape for doing modified time
// comparisons... this might be different if the DAEs have been
// deleted from the install.
String shapeFile( mCachePath );
if ( !Platform::isFile( shapeFile ) )
{
Torque::Path path(shapeFile);
path.setExtension("cached.dts");
shapeFile = path.getFullPath();
if ( !Platform::isFile( shapeFile ) )
{
Con::errorf( "TSLastDetail::update - '%s' could not be found!", mCachePath.c_str() );
return;
}
}
// Do we need to update the imposter?
const String diffuseMapPath = _getDiffuseMapPath();
if ( forceUpdate ||
Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) <= 0 )
_update();
// If the time check fails now then the update must have not worked.
if ( Platform::compareModifiedTimes( diffuseMapPath, shapeFile ) < 0 )
{
Con::errorf( "TSLastDetail::update - Failed to create imposters for '%s'!", mCachePath.c_str() );
return;
}
// Figure out what our vertex format will be.
//
// If we're on SM 3.0 we can do multiple vertex streams
// and the performance win is big as we send 3x less data
// on each imposter instance.
//
// The problem is SM 2.0 won't do this, so we need to
// support fallback to regular single stream imposters.
//
//mImposterVertDecl.copy( *getGFXVertexFormat<ImposterCorner>() );
//mImposterVertDecl.append( *getGFXVertexFormat<ImposterState>(), 1 );
//mImposterVertDecl.getDecl();
mImposterVertDecl.clear();
mImposterVertDecl.copy( *getGFXVertexFormat<ImposterState>() );
// Setup the material for this imposter.
mMaterial = MATMGR->allocateAndRegister( String::EmptyString );
mMaterial->mAutoGenerated = true;
mMaterial->mDiffuseMapFilename[0] = diffuseMapPath;
mMaterial->mNormalMapFilename[0] = _getNormalMapPath();
mMaterial->mImposterLimits.set( (mNumPolarSteps * 2) + 1, mNumEquatorSteps, mPolarAngle, mIncludePoles );
mMaterial->mTranslucent = true;
mMaterial->mTranslucentBlendOp = Material::None;
mMaterial->mTranslucentZWrite = true;
mMaterial->mDoubleSided = true;
mMaterial->mAlphaTest = true;
mMaterial->mAlphaRef = 84;
// Create the material instance.
FeatureSet features = MATMGR->getDefaultFeatures();
features.addFeature( MFT_ImposterVert );
mMatInstance = mMaterial->createMatInstance();
if ( !mMatInstance->init( features, &mImposterVertDecl ) )
{
delete mMatInstance;
mMatInstance = NULL;
}
// Get the diffuse texture and from its size and
// the imposter dimensions we can generate the UVs.
GFXTexHandle diffuseTex( diffuseMapPath, &GFXDefaultStaticDiffuseProfile, String::EmptyString );
Point2I texSize( diffuseTex->getWidth(), diffuseTex->getHeight() );
_validateDim();
S32 downscaledDim = mDim >> GFXTextureManager::getTextureDownscalePower(&GFXDefaultStaticDiffuseProfile);
// Ok... pack in bitmaps till we run out.
Vector<RectF> imposterUVs;
for ( S32 y=0; y+downscaledDim <= texSize.y; )
{
for ( S32 x=0; x+downscaledDim <= texSize.x; )
{
// Store the uv for later lookup.
RectF info;
info.point.set( (F32)x / (F32)texSize.x, (F32)y / (F32)texSize.y );
info.extent.set( (F32)downscaledDim / (F32)texSize.x, (F32)downscaledDim / (F32)texSize.y );
imposterUVs.push_back( info );
x += downscaledDim;
}
y += downscaledDim;
}
AssertFatal( imposterUVs.size() != 0, "hey" );
mMaterial->mImposterUVs = imposterUVs;
}
void TSLastDetail::_validateDim()
{
// Loop till they fit.
S32 newDim = mDim;
while ( true )
{
S32 maxImposters = ( smMaxTexSize / newDim ) * ( smMaxTexSize / newDim );
S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 );
if ( imposterCount <= maxImposters )
break;
// There are too many imposters to fit a single
// texture, so we fail. These imposters are for
// rendering small distant objects. If you need
// a really high resolution imposter or many images
// around the equator and poles, maybe you need a
// custom solution.
newDim /= 2;
}
if ( newDim != mDim )
{
Con::printf( "TSLastDetail::_validateDim - '%s' detail dimensions too big! Reduced from %d to %d.",
mCachePath.c_str(),
mDim, newDim );
mDim = newDim;
}
}
void TSLastDetail::_update()
{
// We're gonna render... make sure we can.
bool sceneBegun = GFX->canCurrentlyRender();
if ( !sceneBegun )
GFX->beginScene();
_validateDim();
Vector<GBitmap*> bitmaps;
Vector<GBitmap*> normalmaps;
// We need to create our own instance to render with.
TSShapeInstance *shape = new TSShapeInstance( mShape, true );
// Animate the shape once.
shape->animate( mDl );
// So we don't have to change it everywhere.
const GFXFormat format = GFXFormatR8G8B8A8;
S32 imposterCount = ( ((2*mNumPolarSteps) + 1 ) * mNumEquatorSteps ) + ( mIncludePoles ? 2 : 0 );
// Figure out the optimal texture size.
Point2I texSize( smMaxTexSize, smMaxTexSize );
while ( true )
{
Point2I halfSize( texSize.x / 2, texSize.y / 2 );
U32 count = ( halfSize.x / mDim ) * ( halfSize.y / mDim );
if ( count < imposterCount )
{
// Try half of the height.
count = ( texSize.x / mDim ) * ( halfSize.y / mDim );
if ( count >= imposterCount )
texSize.y = halfSize.y;
break;
}
texSize = halfSize;
}
GBitmap *imposter = NULL;
GBitmap *normalmap = NULL;
GBitmap destBmp( texSize.x, texSize.y, true, format );
GBitmap destNormal( texSize.x, texSize.y, true, format );
U32 mipLevels = destBmp.getNumMipLevels();
ImposterCapture *imposterCap = new ImposterCapture();
F32 equatorStepSize = M_2PI_F / (F32)mNumEquatorSteps;
static const MatrixF topXfm( EulerF( -M_PI_F / 2.0f, 0, 0 ) );
static const MatrixF bottomXfm( EulerF( M_PI_F / 2.0f, 0, 0 ) );
MatrixF angMat;
F32 polarStepSize = 0.0f;
if ( mNumPolarSteps > 0 )
polarStepSize = -( 0.5f * M_PI_F - mDegToRad( mPolarAngle ) ) / (F32)mNumPolarSteps;
PROFILE_START(TSLastDetail_snapshots);
S32 currDim = mDim;
for ( S32 mip = 0; mip < mipLevels; mip++ )
{
if ( currDim < 1 )
currDim = 1;
dMemset( destBmp.getWritableBits(mip), 0, destBmp.getWidth(mip) * destBmp.getHeight(mip) * GFXFormat_getByteSize( format ) );
dMemset( destNormal.getWritableBits(mip), 0, destNormal.getWidth(mip) * destNormal.getHeight(mip) * GFXFormat_getByteSize( format ) );
bitmaps.clear();
normalmaps.clear();
F32 rotX = 0.0f;
if ( mNumPolarSteps > 0 )
rotX = -( mDegToRad( mPolarAngle ) - 0.5f * M_PI_F );
// We capture the images in a particular order which must
// match the order expected by the imposter renderer.
imposterCap->begin( shape, mDl, currDim, mRadius, mCenter );
for ( U32 j=0; j < (2 * mNumPolarSteps + 1); j++ )
{
F32 rotZ = -M_PI_F / 2.0f;
for ( U32 k=0; k < mNumEquatorSteps; k++ )
{
angMat.mul( MatrixF( EulerF( rotX, 0, 0 ) ),
MatrixF( EulerF( 0, 0, rotZ ) ) );
imposterCap->capture( angMat, &imposter, &normalmap );
bitmaps.push_back( imposter );
normalmaps.push_back( normalmap );
rotZ += equatorStepSize;
}
rotX += polarStepSize;
if ( mIncludePoles )
{
imposterCap->capture( topXfm, &imposter, &normalmap );
bitmaps.push_back(imposter);
normalmaps.push_back( normalmap );
imposterCap->capture( bottomXfm, &imposter, &normalmap );
bitmaps.push_back( imposter );
normalmaps.push_back( normalmap );
}
}
imposterCap->end();
Point2I texSize( destBmp.getWidth(mip), destBmp.getHeight(mip) );
// Ok... pack in bitmaps till we run out.
for ( S32 y=0; y+currDim <= texSize.y; )
{
for ( S32 x=0; x+currDim <= texSize.x; )
{
// Copy the next bitmap to the dest texture.
GBitmap* bmp = bitmaps.first();
bitmaps.pop_front();
destBmp.copyRect( bmp, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip );
delete bmp;
// Copy the next normal to the dest texture.
GBitmap* normalmap = normalmaps.first();
normalmaps.pop_front();
destNormal.copyRect( normalmap, RectI( 0, 0, currDim, currDim ), Point2I( x, y ), 0, mip );
delete normalmap;
// Did we finish?
if ( bitmaps.empty() )
break;
x += currDim;
}
// Did we finish?
if ( bitmaps.empty() )
break;
y += currDim;
}
// Next mip...
currDim /= 2;
}
PROFILE_END(); // TSLastDetail_snapshots
delete imposterCap;
delete shape;
// Should we dump the images?
if ( Con::getBoolVariable( "$TSLastDetail::dumpImposters", false ) )
{
String imposterPath = mCachePath + ".imposter.png";
String normalsPath = mCachePath + ".imposter_normals.png";
FileStream stream;
if ( stream.open( imposterPath, Torque::FS::File::Write ) )
destBmp.writeBitmap( "png", stream );
stream.close();
if ( stream.open( normalsPath, Torque::FS::File::Write ) )
destNormal.writeBitmap( "png", stream );
stream.close();
}
// DEBUG: Some code to force usage of a test image.
//GBitmap* tempMap = GBitmap::load( "./forest/data/test1234.png" );
//tempMap->extrudeMipLevels();
//mTexture.set( tempMap, &GFXDefaultStaticDiffuseProfile, false );
//delete tempMap;
DDSFile *ddsDest = DDSFile::createDDSFileFromGBitmap( &destBmp );
DDSUtil::squishDDS( ddsDest, GFXFormatDXT3 );
DDSFile *ddsNormals = DDSFile::createDDSFileFromGBitmap( &destNormal );
DDSUtil::squishDDS( ddsNormals, GFXFormatDXT5 );
// Finally save the imposters to disk.
FileStream fs;
if ( fs.open( _getDiffuseMapPath(), Torque::FS::File::Write ) )
{
ddsDest->write( fs );
fs.close();
}
if ( fs.open( _getNormalMapPath(), Torque::FS::File::Write ) )
{
ddsNormals->write( fs );
fs.close();
}
delete ddsDest;
delete ddsNormals;
// If we did a begin then end it now.
if ( !sceneBegun )
GFX->endScene();
}
void TSLastDetail::deleteImposterCacheTextures()
{
const String diffuseMap = _getDiffuseMapPath();
if ( diffuseMap.length() )
dFileDelete( diffuseMap );
const String normalMap = _getNormalMapPath();
if ( normalMap.length() )
dFileDelete( normalMap );
}
void TSLastDetail::updateImposterImages( bool forceUpdate )
{
// Can't do it without GFX!
if ( !GFXDevice::devicePresent() )
return;
//D3DPERF_SetMarker( D3DCOLOR_RGBA( 0, 255, 0, 255 ), L"TSLastDetail::makeImposter" );
bool sceneBegun = GFX->canCurrentlyRender();
if ( !sceneBegun )
GFX->beginScene();
Vector<TSLastDetail*>::iterator iter = smLastDetails.begin();
for ( ; iter != smLastDetails.end(); iter++ )
(*iter)->update( forceUpdate );
if ( !sceneBegun )
GFX->endScene();
}
ConsoleFunction(tsUpdateImposterImages, void, 1, 2, "tsUpdateImposterImages( bool forceupdate )")
{
if ( argc > 1 )
TSLastDetail::updateImposterImages( dAtob( argv[1] ) );
else
TSLastDetail::updateImposterImages();
}

View file

@ -0,0 +1,207 @@
//-----------------------------------------------------------------------------
// 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 _TSLASTDETAIL_H_
#define _TSLASTDETAIL_H_
#ifndef _MATHTYPES_H_
#include "math/mathTypes.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _TSRENDERDATA_H_
#include "ts/tsRenderState.h"
#endif
#ifndef _GFXVERTEXFORMAT_H_
#include "gfx/gfxVertexFormat.h"
#endif
#ifndef _SIM_H_
#include "console/simObject.h"
#endif
class TSShape;
class TSRenderState;
class SceneRenderState;
class Material;
class BaseMatInstance;
/// The imposter state vertex format.
GFXDeclareVertexFormat( ImposterState )
{
/// .xyz = imposter center
/// .w = billboard corner... damn SM 2.0
Point3F center;
float corner;
/// .x = scaled half size
/// .y = alpha fade out
float halfSize;
float alpha;
/// The rotation encoded as the up
/// and right vectors... cross FTW.
Point3F upVec;
Point3F rightVec;
};
/// This neat little class renders the object to a texture so that when the object
/// is far away, it can be drawn as a billboard instead of a mesh. This happens
/// when the model is first loaded as to keep the realtime render as fast as possible.
/// It also renders the model from a few different perspectives so that it would actually
/// pass as a model instead of a silly old billboard. In other words, this is an imposter.
class TSLastDetail
{
protected:
/// The shape which we're impostering.
TSShape *mShape;
/// This is the path of the object, which is
/// where we'll be storing our cache for rendered imposters.
String mCachePath;
/// The shape detail level to capture into
/// the imposters.
S32 mDl;
/// The bounding radius of the shape
/// used to size the billboard.
F32 mRadius;
/// The center offset for the bounding
/// sphere used to render the imposter.
Point3F mCenter;
/// The square dimensions of each
/// captured imposter image.
S32 mDim;
/// The number steps around the equator of
/// the globe at which we capture an imposter.
U32 mNumEquatorSteps;
/// The number of steps to go from equator to
/// each polar region (0 means equator only) at
/// which we capture an imposter.
U32 mNumPolarSteps;
/// The angle in radians of sub-polar regions.
F32 mPolarAngle;
/// If true we captures polar images in the
/// imposter texture.
bool mIncludePoles;
/// The combined imposter state and corner data vertex
/// format used for rendering with multiple streams.
GFXVertexFormat mImposterVertDecl;
/// The material for this imposter.
SimObjectPtr<Material> mMaterial;
/// The material instance used to render this imposter.
BaseMatInstance *mMatInstance;
/// This is a global list of all the TSLastDetail
/// objects in the system.
static Vector<TSLastDetail*> smLastDetails;
/// The maximum texture size for a billboard texture.
static const U32 smMaxTexSize = 2048;
/// This update actually regenerates the imposter images.
void _update();
///
void _validateDim();
/// Helper which returns the imposter diffuse map path.
String _getDiffuseMapPath() const { return mCachePath + ".imposter.dds"; }
/// Helper which returns the imposter normal map path.
String _getNormalMapPath() const { return mCachePath + ".imposter_normals.dds"; }
public:
TSLastDetail( TSShape *shape,
const String &cachePath,
U32 numEquatorSteps,
U32 numPolarSteps,
F32 polarAngle,
bool includePoles,
S32 dl,
S32 dim );
~TSLastDetail();
/// Global preference for rendering imposters to shadows.
static bool smCanShadow;
/// Calls update on all TSLastDetail objects in the system.
/// @see update()
static void updateImposterImages( bool forceUpdate = false );
/// Loads the imposter images by reading them from the disk
/// or generating them if the TSShape is more recient than the
/// cached imposter textures.
///
/// This should not be called from within any rendering code.
///
/// @param forceUpdate If true the disk cache is invalidated and
/// new imposter images are rendered.
///
void update( bool forceUpdate = false );
/// Internal function called from TSShapeInstance to
/// submit an imposter render instance.
void render( const TSRenderState &rdata, F32 alpha );
/// Returns the material instance used to render this imposter.
BaseMatInstance* getMatInstance() const { return mMatInstance; }
/// Helper function which deletes the cached imposter
/// texture files from disk.
void deleteImposterCacheTextures();
/// Returns the radius.
/// @see mRadius
F32 getRadius() const { return mRadius; }
};
#endif // _TSLASTDETAIL_H_

View file

@ -0,0 +1,323 @@
//-----------------------------------------------------------------------------
// 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/tsMaterialList.h"
#include "ts/tsShape.h"
#include "materials/matInstance.h"
#include "materials/materialManager.h"
TSMaterialList::TSMaterialList(U32 materialCount,
const char **materialNames,
const U32 * materialFlags,
const U32 * reflectanceMaps,
const U32 * bumpMaps,
const U32 * detailMaps,
const F32 * detailScales,
const F32 * reflectionAmounts)
: MaterialList(materialCount,materialNames),
mNamesTransformed(false)
{
VECTOR_SET_ASSOCIATION(mFlags);
VECTOR_SET_ASSOCIATION(mReflectanceMaps);
VECTOR_SET_ASSOCIATION(mBumpMaps);
VECTOR_SET_ASSOCIATION(mDetailMaps);
VECTOR_SET_ASSOCIATION(mDetailScales);
VECTOR_SET_ASSOCIATION(mReflectionAmounts);
allocate(materialCount);
dMemcpy(mFlags.address(),materialFlags,materialCount*sizeof(U32));
dMemcpy(mReflectanceMaps.address(),reflectanceMaps,materialCount*sizeof(U32));
dMemcpy(mBumpMaps.address(),bumpMaps,materialCount*sizeof(U32));
dMemcpy(mDetailMaps.address(),detailMaps,materialCount*sizeof(U32));
dMemcpy(mDetailScales.address(),detailScales,materialCount*sizeof(F32));
dMemcpy(mReflectionAmounts.address(),reflectionAmounts,materialCount*sizeof(F32));
}
TSMaterialList::TSMaterialList()
: mNamesTransformed(false)
{
VECTOR_SET_ASSOCIATION(mFlags);
VECTOR_SET_ASSOCIATION(mReflectanceMaps);
VECTOR_SET_ASSOCIATION(mBumpMaps);
VECTOR_SET_ASSOCIATION(mDetailMaps);
VECTOR_SET_ASSOCIATION(mDetailScales);
VECTOR_SET_ASSOCIATION(mReflectionAmounts);
}
TSMaterialList::TSMaterialList(const TSMaterialList* pCopy)
: MaterialList(pCopy)
{
VECTOR_SET_ASSOCIATION(mFlags);
VECTOR_SET_ASSOCIATION(mReflectanceMaps);
VECTOR_SET_ASSOCIATION(mBumpMaps);
VECTOR_SET_ASSOCIATION(mDetailMaps);
VECTOR_SET_ASSOCIATION(mDetailScales);
VECTOR_SET_ASSOCIATION(mReflectionAmounts);
mFlags = pCopy->mFlags;
mReflectanceMaps = pCopy->mReflectanceMaps;
mBumpMaps = pCopy->mBumpMaps;
mDetailMaps = pCopy->mDetailMaps;
mDetailScales = pCopy->mDetailScales;
mReflectionAmounts = pCopy->mReflectionAmounts;
mNamesTransformed = pCopy->mNamesTransformed;
}
TSMaterialList::~TSMaterialList()
{
free();
}
void TSMaterialList::free()
{
// these aren't found on our parent, clear them out here to keep in synch
mFlags.clear();
mReflectanceMaps.clear();
mBumpMaps.clear();
mDetailMaps.clear();
mDetailScales.clear();
mReflectionAmounts.clear();
Parent::free();
}
void TSMaterialList::push_back(const String &name, U32 flags, U32 rMap, U32 bMap, U32 dMap, F32 dScale, F32 emapAmount)
{
Parent::push_back(name);
mFlags.push_back(flags);
if (rMap==0xFFFFFFFF)
mReflectanceMaps.push_back(size()-1);
else
mReflectanceMaps.push_back(rMap);
mBumpMaps.push_back(bMap);
mDetailMaps.push_back(dMap);
mDetailScales.push_back(dScale);
mReflectionAmounts.push_back(emapAmount);
}
void TSMaterialList::push_back(const char * name, U32 flags, Material* mat)
{
Parent::push_back(name, mat);
mFlags.push_back(flags);
mReflectanceMaps.push_back(size()-1);
mBumpMaps.push_back(0xFFFFFFFF);
mDetailMaps.push_back(0xFFFFFFFF);
mDetailScales.push_back(1.0f);
mReflectionAmounts.push_back(1.0f);
}
void TSMaterialList::allocate(U32 sz)
{
mFlags.setSize(sz);
mReflectanceMaps.setSize(sz);
mBumpMaps.setSize(sz);
mDetailMaps.setSize(sz);
mDetailScales.setSize(sz);
mReflectionAmounts.setSize(sz);
}
U32 TSMaterialList::getFlags(U32 index)
{
AssertFatal(index < size(),"TSMaterialList::getFlags: index out of range");
return mFlags[index];
}
void TSMaterialList::setFlags(U32 index, U32 value)
{
AssertFatal(index < size(),"TSMaterialList::getFlags: index out of range");
mFlags[index] = value;
}
bool TSMaterialList::write(Stream & s)
{
if (!Parent::write(s))
return false;
U32 i;
for (i=0; i<size(); i++)
s.write(mFlags[i]);
for (i=0; i<size(); i++)
s.write(mReflectanceMaps[i]);
for (i=0; i<size(); i++)
s.write(mBumpMaps[i]);
for (i=0; i<size(); i++)
s.write(mDetailMaps[i]);
// MDF - This used to write mLightmaps
// We never ended up using it but it is
// still part of the version 25 standard
if (TSShape::smVersion == 25)
{
for (i=0; i<size(); i++)
s.write(0xFFFFFFFF);
}
for (i=0; i<size(); i++)
s.write(mDetailScales[i]);
for (i=0; i<size(); i++)
s.write(mReflectionAmounts[i]);
return (s.getStatus() == Stream::Ok);
}
bool TSMaterialList::read(Stream & s)
{
if (!Parent::read(s))
return false;
allocate(size());
U32 i;
if (TSShape::smReadVersion < 2)
{
for (i=0; i<size(); i++)
setFlags(i,S_Wrap|T_Wrap);
}
else
{
for (i=0; i<size(); i++)
s.read(&mFlags[i]);
}
if (TSShape::smReadVersion < 5)
{
for (i=0; i<size(); i++)
{
mReflectanceMaps[i] = i;
mBumpMaps[i] = 0xFFFFFFFF;
mDetailMaps[i] = 0xFFFFFFFF;
}
}
else
{
for (i=0; i<size(); i++)
s.read(&mReflectanceMaps[i]);
for (i=0; i<size(); i++)
s.read(&mBumpMaps[i]);
for (i=0; i<size(); i++)
s.read(&mDetailMaps[i]);
if (TSShape::smReadVersion == 25)
{
U32 dummy = 0;
for (i=0; i<size(); i++)
s.read(&dummy);
}
}
if (TSShape::smReadVersion > 11)
{
for (i=0; i<size(); i++)
s.read(&mDetailScales[i]);
}
else
{
for (i=0; i<size(); i++)
mDetailScales[i] = 1.0f;
}
if (TSShape::smReadVersion > 20)
{
for (i=0; i<size(); i++)
s.read(&mReflectionAmounts[i]);
}
else
{
for (i=0; i<size(); i++)
mReflectionAmounts[i] = 1.0f;
}
if (TSShape::smReadVersion < 16)
{
// make sure emapping is off for translucent materials on old shapes
for (i=0; i<size(); i++)
if (mFlags[i] & TSMaterialList::Translucent)
mFlags[i] |= TSMaterialList::NeverEnvMap;
}
return (s.getStatus() == Stream::Ok);
}
//--------------------------------------------------------------------------
// Sets the specified material in the list to the specified texture. also
// remaps mat instances based on the new texture name. Returns false if
// the specified texture is not valid.
//--------------------------------------------------------------------------
bool TSMaterialList::renameMaterial(U32 i, const String& newName)
{
if (i > size() || newName.isEmpty())
return false;
// Check if already using this name
if (newName.equal(mMaterialNames[i], String::NoCase))
{
// same material, return true since we aren't changing it
return true;
}
// Allow the rename if the new name is mapped, or if there is a diffuse texture
// available (for which a simple Material can be generated in mapMaterial)
String mappedName = MATMGR->getMapEntry(newName);
if (mappedName.isEmpty())
{
GFXTexHandle texHandle;
if (mLookupPath.isEmpty())
{
texHandle.set( newName, &GFXDefaultStaticDiffuseProfile, avar("%s() - handle (line %d)", __FUNCTION__, __LINE__) );
}
else
{
String fullPath = String::ToString( "%s/%s", mLookupPath.c_str(), newName.c_str() );
texHandle.set( fullPath, &GFXDefaultStaticDiffuseProfile, avar("%s() - handle (line %d)", __FUNCTION__, __LINE__) );
}
if (!texHandle.isValid())
return false;
}
// change material name
mMaterialNames[i] = newName;
// Dump the old mat instance and remap the material.
if( mMatInstList[ i ] )
SAFE_DELETE( mMatInstList[ i ] );
mapMaterial( i );
return true;
}
void TSMaterialList::mapMaterial( U32 i )
{
Parent::mapMaterial( i );
BaseMatInstance* matInst = mMatInstList[i];
if (matInst && matInst->getMaterial()->isTranslucent())
mFlags[i] |= Translucent;
}

View file

@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------
// 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 _TSMATERIALLIST_H_
#define _TSMATERIALLIST_H_
#ifndef _MATERIALLIST_H_
#include "materials/materialList.h"
#endif
#ifndef _PATH_H_
#include "core/util/path.h"
#endif
/// Specialized material list for 3space objects.
class TSMaterialList : public MaterialList
{
typedef MaterialList Parent;
Vector<U32> mFlags;
Vector<U32> mReflectanceMaps;
Vector<U32> mBumpMaps;
Vector<U32> mDetailMaps;
Vector<F32> mDetailScales;
Vector<F32> mReflectionAmounts;
bool mNamesTransformed;
void allocate(U32 sz);
public:
enum
{
S_Wrap = BIT(0),
T_Wrap = BIT(1),
Translucent = BIT(2),
Additive = BIT(3),
Subtractive = BIT(4),
SelfIlluminating = BIT(5),
NeverEnvMap = BIT(6),
NoMipMap = BIT(7),
MipMap_ZeroBorder = BIT(8),
AuxiliaryMap = BIT(27) | BIT(28) | BIT(29) | BIT(30) | BIT(31) // DEPRECATED
};
TSMaterialList(U32 materialCount, const char **materialNames, const U32 * materialFlags,
const U32 * reflectanceMaps, const U32 * bumpMaps, const U32 * detailMaps,
const F32 * detailScales, const F32 * reflectionAmounts);
TSMaterialList();
TSMaterialList(const TSMaterialList*);
~TSMaterialList();
void free();
U32 getFlags(U32 index);
void setFlags(U32 index, U32 value);
bool renameMaterial(U32 index, const String& newName); // use to support reskinning
/// pre-load only
void push_back(const String &name, U32 flags,
U32 a=0xFFFFFFFF, U32 b=0xFFFFFFFF, U32 c=0xFFFFFFFF,
F32 dm=1.0f, F32 em=1.0f);
void push_back(const char * name, U32 flags, Material* mat);
/// @name IO
/// Functions for reading/writing to/from streams
/// @{
bool write(Stream &);
bool read(Stream &);
/// @}
protected:
virtual void mapMaterial( U32 index );
};
#endif // _TSMATERIALLIST_H_

3043
Engine/source/ts/tsMesh.cpp Normal file

File diff suppressed because it is too large Load diff

547
Engine/source/ts/tsMesh.h Normal file
View file

@ -0,0 +1,547 @@
//-----------------------------------------------------------------------------
// 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 _TSMESH_H_
#define _TSMESH_H_
#ifndef _STREAM_H_
#include "core/stream/stream.h"
#endif
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
#ifndef _GFXDEVICE_H_
#include "gfx/gfxDevice.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _TSPARSEARRAY_H_
#include "core/tSparseArray.h"
#endif
#include "core/util/safeDelete.h"
#if defined(TORQUE_OS_XENON)
//# define USE_MEM_VERTEX_BUFFERS
#endif
#if defined(USE_MEM_VERTEX_BUFFERS)
# include "gfx/D3D9/360/gfx360MemVertexBuffer.h"
#endif
namespace Opcode { class Model; class MeshInterface; }
namespace IceMaths { class IndexedTriangle; class Point; }
class Convex;
class SceneRenderState;
class SceneObject;
struct MeshRenderInst;
class TSRenderState;
class RenderPassManager;
class TSMaterialList;
class TSShapeInstance;
struct RayInfo;
class ConvexFeature;
class ShapeBase;
struct TSDrawPrimitive
{
enum
{
Triangles = 0 << 30, ///< bits 30 and 31 index element type
Strip = 1 << 30, ///< bits 30 and 31 index element type
Fan = 2 << 30, ///< bits 30 and 31 index element type
Indexed = BIT(29), ///< use glDrawElements if indexed, glDrawArrays o.w.
NoMaterial = BIT(28), ///< set if no material (i.e., texture missing)
MaterialMask = ~(Strip|Fan|Triangles|Indexed|NoMaterial),
TypeMask = Strip|Fan|Triangles
};
S32 start;
S32 numElements;
S32 matIndex; ///< holds material index & element type (see above enum)
};
#if defined(USE_MEM_VERTEX_BUFFERS)
struct __NullVertexStruct {};
typedef GFX360MemVertexBufferHandle<__NullVertexStruct> TSVertexBufferHandle;
#else
typedef GFXVertexBufferDataHandle TSVertexBufferHandle;
#endif
///
class TSMesh
{
friend class TSShape;
public:
struct TSMeshVertexArray;
protected:
U32 meshType;
Box3F mBounds;
Point3F mCenter;
F32 mRadius;
F32 mVisibility;
bool mDynamic;
const GFXVertexFormat *mVertexFormat;
U32 mVertSize;
TSVertexBufferHandle mVB;
GFXPrimitiveBufferHandle mPB;
void _convertToAlignedMeshData( TSMeshVertexArray &vertexData, const Vector<Point3F> &_verts, const Vector<Point3F> &_norms );
void _createVBIB( TSVertexBufferHandle &vb, GFXPrimitiveBufferHandle &pb );
public:
enum
{
/// types...
StandardMeshType = 0,
SkinMeshType = 1,
DecalMeshType = 2,
SortedMeshType = 3,
NullMeshType = 4,
TypeMask = StandardMeshType|SkinMeshType|DecalMeshType|SortedMeshType|NullMeshType,
/// flags (stored with meshType)...
Billboard = BIT(31), HasDetailTexture = BIT(30),
BillboardZAxis = BIT(29), UseEncodedNormals = BIT(28),
FlagMask = Billboard|BillboardZAxis|HasDetailTexture|UseEncodedNormals
};
U32 getMeshType() const { return meshType & TypeMask; }
void setFlags(U32 flag) { meshType |= flag; }
void clearFlags(U32 flag) { meshType &= ~flag; }
U32 getFlags( U32 flag = 0xFFFFFFFF ) const { return meshType & flag; }
const Point3F* getNormals( S32 firstVert );
S32 parentMesh; ///< index into shapes mesh list
S32 numFrames;
S32 numMatFrames;
S32 vertsPerFrame;
/// @name Aligned Vertex Data
/// @{
#pragma pack(1)
struct __TSMeshVertexBase
{
Point3F _vert;
F32 _tangentW;
Point3F _normal;
Point3F _tangent;
Point2F _tvert;
const Point3F &vert() const { return _vert; }
void vert(const Point3F &v) { _vert = v; }
const Point3F &normal() const { return _normal; }
void normal(const Point3F &n) { _normal = n; }
Point4F tangent() const { return Point4F(_tangent.x, _tangent.y, _tangent.z, _tangentW); }
void tangent(const Point4F &t) { _tangent = t.asPoint3F(); _tangentW = t.w; }
const Point2F &tvert() const { return _tvert; }
void tvert(const Point2F &tv) { _tvert = tv;}
// Don't call these unless it's actually a __TSMeshVertex_3xUVColor, for real.
// We don't want a vftable for virtual methods.
Point2F &tvert2() const { return *reinterpret_cast<Point2F *>(reinterpret_cast<U8 *>(const_cast<__TSMeshVertexBase *>(this)) + 0x30); }
void tvert2(const Point2F &tv) { (*reinterpret_cast<Point2F *>(reinterpret_cast<U8 *>(this) + 0x30)) = tv; }
GFXVertexColor &color() const { return *reinterpret_cast<GFXVertexColor *>(reinterpret_cast<U8 *>(const_cast<__TSMeshVertexBase *>(this)) + 0x38); }
void color(const GFXVertexColor &c) { (*reinterpret_cast<GFXVertexColor *>(reinterpret_cast<U8 *>(this) + 0x38)) = c; }
};
struct __TSMeshVertex_3xUVColor : public __TSMeshVertexBase
{
Point2F _tvert2;
GFXVertexColor _color;
F32 _tvert3; // Unused, but needed for alignment purposes
};
#pragma pack()
struct TSMeshVertexArray
{
protected:
U8 *base;
dsize_t vertSz;
bool vertexDataReady;
U32 numElements;
public:
TSMeshVertexArray() : base(NULL), vertexDataReady(false), numElements(0) {}
virtual ~TSMeshVertexArray() { set(NULL, 0, 0); }
virtual void set(void *b, dsize_t s, U32 n, bool autoFree = true )
{
if(base && autoFree)
dFree_aligned(base);
base = reinterpret_cast<U8 *>(b);
vertSz = s;
numElements = n;
}
// Vector-like interface
__TSMeshVertexBase &operator[](int idx) const { AssertFatal(idx < numElements, "Out of bounds access!"); return *reinterpret_cast<__TSMeshVertexBase *>(base + idx * vertSz); }
__TSMeshVertexBase *address() const { return reinterpret_cast<__TSMeshVertexBase *>(base); }
U32 size() const { return numElements; }
dsize_t mem_size() const { return numElements * vertSz; }
dsize_t vertSize() const { return vertSz; }
bool isReady() const { return vertexDataReady; }
void setReady(bool r) { vertexDataReady = r; }
};
bool mHasColor;
bool mHasTVert2;
TSMeshVertexArray mVertexData;
dsize_t mNumVerts;
virtual void convertToAlignedMeshData();
/// @}
/// @name Vertex data
/// @{
template<class T>
class FreeableVector : public Vector<T>
{
public:
bool free_memory() { return Vector<T>::resize(0); }
FreeableVector<T>& operator=(const Vector<T>& p) { Vector<T>::operator=(p); return *this; }
FreeableVector<T>& operator=(const FreeableVector<T>& p) { Vector<T>::operator=(p); return *this; }
};
FreeableVector<Point3F> verts;
FreeableVector<Point3F> norms;
FreeableVector<Point2F> tverts;
FreeableVector<Point4F> tangents;
// Optional second texture uvs.
FreeableVector<Point2F> tverts2;
// Optional vertex colors data.
FreeableVector<ColorI> colors;
/// @}
Vector<TSDrawPrimitive> primitives;
Vector<U8> encodedNorms;
Vector<U32> indices;
/// billboard data
Point3F billboardAxis;
/// @name Convex Hull Data
/// Convex hulls are convex (no angles >= 180º) meshes used for collision
/// @{
Vector<Point3F> planeNormals;
Vector<F32> planeConstants;
Vector<U32> planeMaterials;
S32 planesPerFrame;
U32 mergeBufferStart;
/// @}
/// @name Render Methods
/// @{
/// This is used by sgShadowProjector to render the
/// mesh directly, skipping the render manager.
virtual void render( TSVertexBufferHandle &vb, GFXPrimitiveBufferHandle &pb );
void innerRender( TSVertexBufferHandle &vb, GFXPrimitiveBufferHandle &pb );
virtual void render( TSMaterialList *,
const TSRenderState &data,
bool isSkinDirty,
const Vector<MatrixF> &transforms,
TSVertexBufferHandle &vertexBuffer,
GFXPrimitiveBufferHandle &primitiveBuffer );
void innerRender( TSMaterialList *, const TSRenderState &data, TSVertexBufferHandle &vb, GFXPrimitiveBufferHandle &pb );
/// @}
/// @name Material Methods
/// @{
void setFade( F32 fade ) { mVisibility = fade; }
void clearFade() { setFade( 1.0f ); }
/// @}
/// @name Collision Methods
/// @{
virtual bool buildPolyList( S32 frame, AbstractPolyList * polyList, U32 & surfaceKey, TSMaterialList* materials );
virtual bool getFeatures( S32 frame, const MatrixF&, const VectorF&, ConvexFeature*, U32 &surfaceKey );
virtual void support( S32 frame, const Point3F &v, F32 *currMaxDP, Point3F *currSupport );
virtual bool castRay( S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo, TSMaterialList* materials );
virtual bool castRayRendered( S32 frame, const Point3F & start, const Point3F & end, RayInfo * rayInfo, TSMaterialList* materials );
virtual bool buildConvexHull(); ///< returns false if not convex (still builds planes)
bool addToHull( U32 idx0, U32 idx1, U32 idx2 );
/// @}
/// @name Bounding Methods
/// calculate and get bounding information
/// @{
void computeBounds();
virtual void computeBounds( const MatrixF &transform, Box3F &bounds, S32 frame = 0, Point3F *center = NULL, F32 *radius = NULL );
void computeBounds( const Point3F *, S32 numVerts, S32 stride, const MatrixF &transform, Box3F &bounds, Point3F *center, F32 *radius );
const Box3F& getBounds() const { return mBounds; }
const Point3F& getCenter() const { return mCenter; }
F32 getRadius() const { return mRadius; }
virtual S32 getNumPolys() const;
static U8 encodeNormal( const Point3F &normal );
static const Point3F& decodeNormal( U8 ncode ) { return smU8ToNormalTable[ncode]; }
/// @}
/// persist methods...
virtual void assemble( bool skip );
static TSMesh* assembleMesh( U32 meshType, bool skip );
virtual void disassemble();
void createVBIB();
void createTangents(const Vector<Point3F> &_verts, const Vector<Point3F> &_norms);
void findTangent( U32 index1,
U32 index2,
U32 index3,
Point3F *tan0,
Point3F *tan1,
const Vector<Point3F> &_verts);
/// on load...optionally convert primitives to other form
static bool smUseTriangles;
static bool smUseOneStrip;
static S32 smMinStripSize;
static bool smUseEncodedNormals;
/// Enables mesh instancing on non-skin meshes that
/// have less that this count of verts.
static S32 smMaxInstancingVerts;
/// convert primitives on load...
void convertToTris(const TSDrawPrimitive *primitivesIn, const S32 *indicesIn,
S32 numPrimIn, S32 & numPrimOut, S32 & numIndicesOut,
TSDrawPrimitive *primitivesOut, S32 *indicesOut) const;
void convertToSingleStrip(const TSDrawPrimitive *primitivesIn, const S32 *indicesIn,
S32 numPrimIn, S32 &numPrimOut, S32 &numIndicesOut,
TSDrawPrimitive *primitivesOut, S32 *indicesOut) const;
void leaveAsMultipleStrips(const TSDrawPrimitive *primitivesIn, const S32 *indicesIn,
S32 numPrimIn, S32 &numPrimOut, S32 &numIndicesOut,
TSDrawPrimitive *primitivesOut, S32 *indicesOut) const;
/// methods used during assembly to share vertexand other info
/// between meshes (and for skipping detail levels on load)
S32* getSharedData32( S32 parentMesh, S32 size, S32 **source, bool skip );
S8* getSharedData8( S32 parentMesh, S32 size, S8 **source, bool skip );
/// @name Assembly Variables
/// variables used during assembly (for skipping mesh detail levels
/// on load and for sharing verts between meshes)
/// @{
static Vector<Point3F*> smVertsList;
static Vector<Point3F*> smNormsList;
static Vector<U8*> smEncodedNormsList;
static Vector<Point2F*> smTVertsList;
// Optional second texture uvs.
static Vector<Point2F*> smTVerts2List;
// Optional vertex colors.
static Vector<ColorI*> smColorsList;
static Vector<bool> smDataCopied;
static const Point3F smU8ToNormalTable[];
/// @}
TSMesh();
virtual ~TSMesh();
Opcode::Model *mOptTree;
Opcode::MeshInterface* mOpMeshInterface;
IceMaths::IndexedTriangle* mOpTris;
IceMaths::Point* mOpPoints;
void prepOpcodeCollision();
bool buildConvexOpcode( const MatrixF &mat, const Box3F &bounds, Convex *c, Convex *list );
bool buildPolyListOpcode( const S32 od, AbstractPolyList *polyList, const Box3F &nodeBox, TSMaterialList *materials );
bool castRayOpcode( const Point3F &start, const Point3F &end, RayInfo *rayInfo, TSMaterialList *materials );
static const F32 VISIBILITY_EPSILON;
};
class TSSkinMesh : public TSMesh
{
public:
struct BatchData
{
enum Constants
{
maxBonePerVert = 16, // Abitrarily chosen
};
/// @name Batch by vertex
/// These are used for batches where each element is a vertex, built by
/// iterating over 0..maxBonePerVert bone transforms
/// @{
struct TransformOp
{
S32 transformIndex;
F32 weight;
TransformOp() : transformIndex( -1 ), weight( -1.0f ) {}
TransformOp( const S32 tIdx, const F32 w ) : transformIndex( tIdx ), weight( w ) {};
};
struct BatchedVertex
{
S32 vertexIndex;
S32 transformCount;
TransformOp transform[maxBonePerVert];
BatchedVertex() : vertexIndex( -1 ), transformCount( -1 ) {}
};
Vector<BatchedVertex> vertexBatchOperations;
/// @}
/// @name Batch by Bone Transform
/// These are used for batches where each element is a bone transform,
/// and verts/normals are batch transformed against each element
/// @{
#pragma pack(1)
dALIGN(
struct BatchedVertWeight
{
Point3F vert; // Do not change the ordering of these members
F32 weight;
Point3F normal;
S32 vidx;
}
); // dALIGN
#pragma pack()
struct BatchedTransform
{
public:
BatchedVertWeight *alignedMem;
dsize_t numElements;
Vector<BatchedVertWeight> *_tmpVec;
BatchedTransform() : alignedMem(NULL), numElements(0), _tmpVec(NULL) {}
virtual ~BatchedTransform()
{
if(alignedMem)
dFree_aligned(alignedMem);
alignedMem = NULL;
SAFE_DELETE(_tmpVec);
}
};
SparseArray<BatchedTransform> transformBatchOperations;
Vector<S32> transformKeys;
/// @}
// # = num bones
Vector<S32> nodeIndex;
Vector<MatrixF> initialTransforms;
// # = numverts
Vector<Point3F> initialVerts;
Vector<Point3F> initialNorms;
};
/// This method will build the batch operations and prepare the BatchData
/// for use.
void createBatchData();
virtual void convertToAlignedMeshData();
public:
typedef TSMesh Parent;
/// Structure containing data needed to batch skinning
BatchData batchData;
bool batchDataInitialized;
/// vectors that define the vertex, weight, bone tuples
Vector<F32> weight;
Vector<S32> boneIndex;
Vector<S32> vertexIndex;
/// set verts and normals...
void updateSkin( const Vector<MatrixF> &transforms, TSVertexBufferHandle &instanceVB, GFXPrimitiveBufferHandle &instancePB );
// render methods..
void render( TSVertexBufferHandle &instanceVB, GFXPrimitiveBufferHandle &instancePB );
void render( TSMaterialList *,
const TSRenderState &data,
bool isSkinDirty,
const Vector<MatrixF> &transforms,
TSVertexBufferHandle &vertexBuffer,
GFXPrimitiveBufferHandle &primitiveBuffer );
// collision methods...
bool buildPolyList( S32 frame, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials );
bool castRay( S32 frame, const Point3F &start, const Point3F &end, RayInfo *rayInfo, TSMaterialList *materials );
bool buildConvexHull(); // does nothing, skins don't use this
void computeBounds( const MatrixF &transform, Box3F &bounds, S32 frame, Point3F *center, F32 *radius );
/// persist methods...
void assemble( bool skip );
void disassemble();
/// variables used during assembly (for skipping mesh detail levels
/// on load and for sharing verts between meshes)
static Vector<MatrixF*> smInitTransformList;
static Vector<S32*> smVertexIndexList;
static Vector<S32*> smBoneIndexList;
static Vector<F32*> smWeightList;
static Vector<S32*> smNodeIndexList;
TSSkinMesh();
};
#endif // _TSMESH_H_

View file

@ -0,0 +1,940 @@
//-----------------------------------------------------------------------------
// 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 "console/consoleTypes.h"
#include "core/resourceManager.h"
#include "ts/tsShapeConstruct.h"
#include "console/engineAPI.h"
// define macros required for ConvexDecomp headers
#if defined( _WIN32 )
#define WIN32
#elif defined( __MACOSX__ )
#define APPLE
#endif
#include "convexDecomp/NvFloatMath.h"
#include "convexDecomp/NvConvexDecomposition.h"
#include "convexDecomp/NvStanHull.h"
//-----------------------------------------------------------------------------
static const Point3F sFacePlanes[] = {
Point3F( -1.0f, 0.0f, 0.0f ),
Point3F( 1.0f, 0.0f, 0.0f ),
Point3F( 0.0f, -1.0f, 0.0f ),
Point3F( 0.0f, 1.0f, 0.0f ),
Point3F( 0.0f, 0.0f, -1.0f ),
Point3F( 0.0f, 0.0f, 1.0f ),
};
static const Point3F sXEdgePlanes[] = {
Point3F( 0.0f, -0.7071f, -0.7071f ),
Point3F( 0.0f, -0.7071f, 0.7071f ),
Point3F( 0.0f, 0.7071f, -0.7071f ),
Point3F( 0.0f, 0.7071f, 0.7071f ),
};
static const Point3F sYEdgePlanes[] = {
Point3F( -0.7071f, 0.0f, -0.7071f ),
Point3F( -0.7071f, 0.0f, 0.7071f ),
Point3F( 0.7071f, 0.0f, -0.7071f ),
Point3F( 0.7071f, 0.0f, 0.7071f ),
};
static const Point3F sZEdgePlanes[] = {
Point3F( -0.7071f, -0.7071f, 0.0f ),
Point3F( -0.7071f, 0.7071f, 0.0f ),
Point3F( 0.7071f, -0.7071f, 0.0f ),
Point3F( 0.7071f, 0.7071f, 0.0f ),
};
static const Point3F sCornerPlanes[] = {
Point3F( -0.5774f, -0.5774f, -0.5774f ),
Point3F( -0.5774f, -0.5774f, 0.5774f ),
Point3F( -0.5774f, 0.5774f, -0.5774f ),
Point3F( -0.5774f, 0.5774f, 0.5774f ),
Point3F( 0.5774f, -0.5774f, -0.5774f ),
Point3F( 0.5774f, -0.5774f, 0.5774f ),
Point3F( 0.5774f, 0.5774f, -0.5774f ),
Point3F( 0.5774f, 0.5774f, 0.5774f ),
};
//-----------------------------------------------------------------------------
/** A helper class for fitting primitives (Box, Sphere, Capsule) to a triangulated mesh */
struct PrimFit
{
MatrixF mBoxTransform;
Point3F mBoxSides;
Point3F mSphereCenter;
F32 mSphereRadius;
MatrixF mCapTransform;
F32 mCapRadius;
F32 mCapHeight;
public:
PrimFit() :
mBoxTransform(true), mBoxSides(1,1,1),
mSphereCenter(0,0,0), mSphereRadius(1),
mCapTransform(true), mCapRadius(1), mCapHeight(1)
{
}
inline F32 getBoxVolume() const { return mBoxSides.x * mBoxSides.y * mBoxSides.z; }
inline F32 getSphereVolume() const { return 4.0f / 3.0f * M_PI * mPow( mSphereRadius, 3 ); }
inline F32 getCapsuleVolume() const { return 2 * M_PI * mPow( mCapRadius, 2 ) * (4.0f / 3.0f * mCapRadius + mCapHeight); }
void fitBox( U32 vertCount, const F32* verts )
{
CONVEX_DECOMPOSITION::fm_computeBestFitOBB( vertCount, verts, sizeof(F32)*3, (F32*)mBoxSides, (F32*)mBoxTransform );
mBoxTransform.transpose();
}
void fitSphere( U32 vertCount, const F32* verts )
{
mSphereRadius = CONVEX_DECOMPOSITION::fm_computeBestFitSphere( vertCount, verts, sizeof(F32)*3, (F32*)mSphereCenter );
}
void fitCapsule( U32 vertCount, const F32* verts )
{
CONVEX_DECOMPOSITION::fm_computeBestFitCapsule( vertCount, verts, sizeof(F32)*3, mCapRadius, mCapHeight, (F32*)mCapTransform );
mCapTransform.transpose();
}
};
class MeshFit
{
public:
enum eMeshType
{
Box = 0,
Sphere,
Capsule,
Hull,
};
struct Mesh
{
eMeshType type;
MatrixF transform;
TSMesh *tsmesh;
};
private:
TSShape *mShape; ///!< Source geometry shape
Vector<Point3F> mVerts; ///!< Source geometry verts (all meshes)
Vector<U32> mIndices; ///!< Source geometry indices (triangle lists, all meshes)
bool mIsReady; ///!< Flag indicating whether we are ready to fit/create meshes
Vector<Mesh> mMeshes; ///!< Fitted meshes
void addSourceMesh( const TSShape::Object& obj, const TSMesh* mesh );
TSMesh* initMeshFromFile( const String& filename ) const;
TSMesh* createTriMesh( F32* verts, S32 numVerts, U32* indices, S32 numTris ) const;
F32 maxDot( const VectorF& v ) const;
void fitK_DOP( const Vector<Point3F>& planes );
public:
MeshFit(TSShape* shape) : mShape(shape), mIsReady(false) { }
void setReady() { mIsReady = true; }
bool isReady() const { return mIsReady; }
void initSourceGeometry( const String& target );
S32 getMeshCount() const { return mMeshes.size(); }
Mesh* getMesh( S32 index ) { return &(mMeshes[index]); }
// Box
void addBox( const Point3F& sides, const MatrixF& mat );
void fitOBB();
// Sphere
void addSphere( F32 radius, const Point3F& center );
void fitSphere();
// Capsule
void addCapsule( F32 radius, F32 height, const MatrixF& mat );
void fitCapsule();
// k-DOP
void fit10_DOP_X();
void fit10_DOP_Y();
void fit10_DOP_Z();
void fit18_DOP();
void fit26_DOP();
// Convex Hulls
void fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThreshold, U32 maxHullVerts,
F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError );
};
void MeshFit::initSourceGeometry( const String& target )
{
mMeshes.clear();
mVerts.clear();
mIndices.clear();
if ( target.equal( "bounds", String::NoCase ) )
{
// Add all geometry in the highest detail level
S32 dl = 0;
S32 ss = mShape->details[dl].subShapeNum;
if ( ss < 0 )
return;
S32 od = mShape->details[dl].objectDetailNum;
S32 start = mShape->subShapeFirstObject[ss];
S32 end = start + mShape->subShapeNumObjects[ss];
for ( S32 i = start; i < end; i++ )
{
const TSShape::Object &obj = mShape->objects[i];
const TSMesh* mesh = ( od < obj.numMeshes ) ? mShape->meshes[obj.startMeshIndex + od] : NULL;
if ( mesh )
addSourceMesh( obj, mesh );
}
}
else
{
// Add highest detail mesh from this object
S32 objIndex = mShape->findObject( target );
if ( objIndex == -1 )
return;
const TSShape::Object &obj = mShape->objects[objIndex];
for ( S32 i = 0; i < obj.numMeshes; i++ )
{
const TSMesh* mesh = mShape->meshes[obj.startMeshIndex + i];
if ( mesh )
{
addSourceMesh( obj, mesh );
break;
}
}
}
mIsReady = ( !mVerts.empty() && !mIndices.empty() );
}
void MeshFit::addSourceMesh( const TSShape::Object& obj, const TSMesh* mesh )
{
// Add indices
S32 indicesBase = mIndices.size();
for ( S32 i = 0; i < mesh->primitives.size(); i++ )
{
const TSDrawPrimitive& draw = mesh->primitives[i];
if ( (draw.matIndex & TSDrawPrimitive::TypeMask) == TSDrawPrimitive::Triangles )
{
mIndices.merge( &mesh->indices[draw.start], draw.numElements );
}
else
{
U32 idx0 = mesh->indices[draw.start + 0];
U32 idx1;
U32 idx2 = mesh->indices[draw.start + 1];
U32 *nextIdx = &idx1;
for ( S32 j = 2; j < draw.numElements; j++ )
{
*nextIdx = idx2;
nextIdx = (U32*) ( (dsize_t)nextIdx ^ (dsize_t)&idx0 ^ (dsize_t)&idx1);
idx2 = mesh->indices[draw.start + j];
if ( idx0 == idx1 || idx0 == idx2 || idx1 == idx2 )
continue;
mIndices.push_back( idx0 );
mIndices.push_back( idx1 );
mIndices.push_back( idx2 );
}
}
}
// Offset indices for already added verts
for ( S32 j = indicesBase; j < mIndices.size(); j++ )
mIndices[j] += mVerts.size();
// Add verts
S32 count, stride;
U8* pVert;
if ( mesh->mVertexData.isReady() )
{
count = mesh->mVertexData.size();
stride = mesh->mVertexData.vertSize();
pVert = (U8*)mesh->mVertexData.address();
}
else
{
count = mesh->verts.size();
stride = sizeof(Point3F);
pVert = (U8*)mesh->verts.address();
}
MatrixF objMat;
mShape->getNodeWorldTransform( obj.nodeIndex, &objMat );
mVerts.reserve( mVerts.size() + count );
for ( S32 j = 0; j < count; j++, pVert += stride )
{
mVerts.increment();
objMat.mulP( *(Point3F*)pVert, &mVerts.last() );
}
}
TSMesh* MeshFit::initMeshFromFile( const String& filename ) const
{
// Open the source shape file and make a copy of the mesh
Resource<TSShape> hShape = ResourceManager::get().load(filename);
if (!bool(hShape) || !((TSShape*)hShape)->meshes.size())
{
Con::errorf("TSShape::createMesh: Could not load source mesh from %s", filename.c_str());
return NULL;
}
TSMesh* srcMesh = ((TSShape*)hShape)->meshes[0];
return mShape->copyMesh( srcMesh );
}
TSMesh* MeshFit::createTriMesh( F32* verts, S32 numVerts, U32* indices, S32 numTris ) const
{
TSMesh* mesh = mShape->copyMesh( NULL );
mesh->numFrames = 1;
mesh->numMatFrames = 1;
mesh->vertsPerFrame = numVerts;
mesh->setFlags(0);
mesh->mHasColor = false;
mesh->mHasTVert2 = false;
mesh->mNumVerts = numVerts;
mesh->indices.reserve( numTris * 3 );
for ( S32 i = 0; i < numTris; i++ )
{
mesh->indices.push_back( indices[i*3 + 0] );
mesh->indices.push_back( indices[i*3 + 2] );
mesh->indices.push_back( indices[i*3 + 1] );
}
mesh->verts.set( verts, numVerts );
// Compute mesh normals
mesh->norms.setSize( mesh->verts.size() );
for (S32 iNorm = 0; iNorm < mesh->norms.size(); iNorm++)
mesh->norms[iNorm] = Point3F::Zero;
// Sum triangle normals for each vertex
for (S32 iInd = 0; iInd < mesh->indices.size(); iInd += 3)
{
// Compute the normal for this triangle
S32 idx0 = mesh->indices[iInd + 0];
S32 idx1 = mesh->indices[iInd + 1];
S32 idx2 = mesh->indices[iInd + 2];
const Point3F& v0 = mesh->verts[idx0];
const Point3F& v1 = mesh->verts[idx1];
const Point3F& v2 = mesh->verts[idx2];
Point3F n;
mCross(v2 - v0, v1 - v0, &n);
n.normalize(); // remove this to use 'weighted' normals (large triangles will have more effect)
mesh->norms[idx0] += n;
mesh->norms[idx1] += n;
mesh->norms[idx2] += n;
}
// Normalize the vertex normals (this takes care of averaging the triangle normals)
for (S32 iNorm = 0; iNorm < mesh->norms.size(); iNorm++)
mesh->norms[iNorm].normalize();
// Set some dummy UVs
mesh->tverts.setSize( numVerts );
for ( S32 j = 0; j < mesh->tverts.size(); j++ )
mesh->tverts[j].set( 0, 0 );
// Add a single triangle-list primitive
mesh->primitives.increment();
mesh->primitives.last().start = 0;
mesh->primitives.last().numElements = mesh->indices.size();
mesh->primitives.last().matIndex = TSDrawPrimitive::Triangles |
TSDrawPrimitive::Indexed |
TSDrawPrimitive::NoMaterial;
mesh->createTangents( mesh->verts, mesh->norms );
mesh->encodedNorms.set( NULL,0 );
return mesh;
}
F32 MeshFit::maxDot( const VectorF& v ) const
{
F32 maxDot = -FLT_MAX;
for ( S32 i = 0; i < mVerts.size(); i++ )
maxDot = getMax( maxDot, mDot( v, mVerts[i] ) );
return maxDot;
}
//---------------------------
// Best-fit oriented bounding box
void MeshFit::addBox( const Point3F& sides, const MatrixF& mat )
{
TSMesh* mesh = initMeshFromFile( "core/art/shapes/unit_cube.dts" );
if ( !mesh )
return;
for ( S32 i = 0; i < mesh->mVertexData.size(); i++ )
{
Point3F v = mesh->mVertexData[i].vert();
v.convolve( sides );
mesh->mVertexData[i].vert( v );
}
mesh->computeBounds();
mMeshes.increment();
mMeshes.last().type = MeshFit::Box;
mMeshes.last().transform = mat;
mMeshes.last().tsmesh = mesh;
}
void MeshFit::fitOBB()
{
PrimFit primFitter;
primFitter.fitBox( mVerts.size(), (F32*)mVerts.address() );
addBox( primFitter.mBoxSides, primFitter.mBoxTransform );
}
//---------------------------
// Best-fit sphere
void MeshFit::addSphere( F32 radius, const Point3F& center )
{
TSMesh* mesh = initMeshFromFile( "core/art/shapes/unit_sphere.dts" );
if ( !mesh )
return;
for ( S32 i = 0; i < mesh->mVertexData.size(); i++ )
{
Point3F v = mesh->mVertexData[i].vert();
mesh->mVertexData[i].vert( v * radius );
}
mesh->computeBounds();
mMeshes.increment();
mMeshes.last().type = MeshFit::Sphere;
mMeshes.last().transform.identity();
mMeshes.last().transform.setPosition( center );
mMeshes.last().tsmesh = mesh;
}
void MeshFit::fitSphere()
{
PrimFit primFitter;
primFitter.fitSphere( mVerts.size(), (F32*)mVerts.address() );
addSphere( primFitter.mSphereRadius, primFitter.mSphereCenter );
}
//---------------------------
// Best-fit capsule
void MeshFit::addCapsule( F32 radius, F32 height, const MatrixF& mat )
{
TSMesh* mesh = initMeshFromFile( "core/art/shapes/unit_capsule.dts" );
if ( !mesh )
return;
// Translate and scale the mesh verts
height = mMax( 0, height );
F32 offset = ( height / ( 2 * radius ) ) - 0.5f;
for ( S32 i = 0; i < mesh->mVertexData.size(); i++ )
{
Point3F v = mesh->mVertexData[i].vert();
v.y += ( ( v.y > 0 ) ? offset : -offset );
mesh->mVertexData[i].vert( v * radius );
}
mesh->computeBounds();
mMeshes.increment();
mMeshes.last().type = MeshFit::Capsule;
mMeshes.last().transform = mat;
mMeshes.last().tsmesh = mesh;
}
void MeshFit::fitCapsule()
{
PrimFit primFitter;
primFitter.fitCapsule( mVerts.size(), (F32*)mVerts.address() );
addCapsule( primFitter.mCapRadius, primFitter.mCapHeight, primFitter.mCapTransform );
}
//---------------------------
// Best-fit k-discrete-oriented-polytope (where k is the number of axis-aligned planes)
// All faces + 4 edges (aligned to X axis) of the unit cube
void MeshFit::fit10_DOP_X()
{
Vector<Point3F> planes;
planes.setSize( 10 );
dCopyArray( planes.address(), sFacePlanes, 6 );
dCopyArray( planes.address()+6, sXEdgePlanes, 4 );
fitK_DOP( planes );
}
// All faces + 4 edges (aligned to Y axis) of the unit cube
void MeshFit::fit10_DOP_Y()
{
Vector<Point3F> planes;
planes.setSize( 10 );
dCopyArray( planes.address(), sFacePlanes, 6 );
dCopyArray( planes.address()+6, sYEdgePlanes, 4 );
fitK_DOP( planes );
}
// All faces + 4 edges (aligned to Z axis) of the unit cube
void MeshFit::fit10_DOP_Z()
{
Vector<Point3F> planes;
planes.setSize( 10 );
dCopyArray( planes.address(), sFacePlanes, 6 );
dCopyArray( planes.address()+6, sZEdgePlanes, 4 );
fitK_DOP( planes );
}
// All faces and edges of the unit cube
void MeshFit::fit18_DOP()
{
Vector<Point3F> planes;
planes.setSize( 18 );
dCopyArray( planes.address(), sFacePlanes, 6 );
dCopyArray( planes.address()+6, sXEdgePlanes, 4 );
dCopyArray( planes.address()+10, sYEdgePlanes, 4 );
dCopyArray( planes.address()+14, sZEdgePlanes, 4 );
fitK_DOP( planes );
}
// All faces, edges and corners of the unit cube
void MeshFit::fit26_DOP()
{
Vector<Point3F> planes;
planes.setSize( 26 );
dCopyArray( planes.address(), sFacePlanes, 6 );
dCopyArray( planes.address()+6, sXEdgePlanes, 4 );
dCopyArray( planes.address()+10, sYEdgePlanes, 4 );
dCopyArray( planes.address()+14, sZEdgePlanes, 4 );
dCopyArray( planes.address()+18, sCornerPlanes, 8 );
fitK_DOP( planes );
}
void MeshFit::fitK_DOP( const Vector<Point3F>& planes )
{
// Push the planes up against the mesh
Vector<F32> planeDs;
for ( S32 i = 0; i < planes.size(); i++ )
planeDs.push_back( maxDot( planes[i] ) );
// Collect the intersection points of any 3 planes that lie inside
// the maximum distances found above
Vector<Point3F> points;
for ( S32 i = 0; i < planes.size()-2; i++ )
{
for ( S32 j = i+1; j < planes.size()-1; j++ )
{
for ( S32 k = j+1; k < planes.size(); k++ )
{
Point3F v23 = mCross( planes[j], planes[k] );
F32 denom = mDot( planes[i], v23 );
if ( denom == 0 )
continue;
Point3F v31 = mCross( planes[k], planes[i] );
Point3F v12 = mCross( planes[i], planes[j] );
Point3F p = ( planeDs[i]*v23 + planeDs[j]*v31 + planeDs[k]*v12 ) / denom;
// Ignore intersection points outside the volume
// described by the planes
bool addPoint = true;
for ( S32 n = 0; n < planes.size(); n++ )
{
if ( ( mDot( p, planes[n] ) - planeDs[n] ) > 0.005f )
{
addPoint = false;
break;
}
}
if ( addPoint )
points.push_back( p );
}
}
}
// Create a convex hull from the point set
CONVEX_DECOMPOSITION::HullDesc hd;
hd.mVcount = points.size();
hd.mVertices = (F32*)points.address();
hd.mVertexStride = sizeof(Point3F);
hd.mMaxVertices = 64;
hd.mSkinWidth = 0.0f;
CONVEX_DECOMPOSITION::HullLibrary hl;
CONVEX_DECOMPOSITION::HullResult result;
hl.CreateConvexHull( hd, result );
// Create TSMesh from convex hull
mMeshes.increment();
mMeshes.last().type = MeshFit::Hull;
mMeshes.last().transform.identity();
mMeshes.last().tsmesh = createTriMesh( result.mOutputVertices, result.mNumOutputVertices,
result.mIndices, result.mNumFaces );
mMeshes.last().tsmesh->computeBounds();
}
//---------------------------
// Best-fit set of convex hulls
void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThreshold, U32 maxHullVerts,
F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError )
{
const F32 SkinWidth = 0.0f;
const F32 SplitThreshold = 2.0f;
CONVEX_DECOMPOSITION::iConvexDecomposition *ic = CONVEX_DECOMPOSITION::createConvexDecomposition();
for ( S32 i = 0; i < mIndices.size(); i += 3 )
{
ic->addTriangle( (F32*)mVerts[mIndices[i]],
(F32*)mVerts[mIndices[i+1]],
(F32*)mVerts[mIndices[i+2]] );
}
ic->computeConvexDecomposition(
SkinWidth,
depth,
maxHullVerts,
concavityThreshold,
mergeThreshold,
SplitThreshold,
true,
false,
false );
// Add a TSMesh for each hull
for ( S32 i = 0; i < ic->getHullCount(); i++ )
{
CONVEX_DECOMPOSITION::ConvexHullResult result;
ic->getConvexHullResult( i, result );
eMeshType meshType = MeshFit::Hull;
// Check if we can use a box, sphere or capsule primitive for this hull
if (( boxMaxError > 0 ) || ( sphereMaxError > 0 ) || ( capsuleMaxError > 0 ))
{
// Compute error between actual mesh and fitted primitives
F32 meshVolume = CONVEX_DECOMPOSITION::fm_computeMeshVolume( result.mVertices, result.mTcount, result.mIndices );
PrimFit primFitter;
F32 boxError = 100.0f, sphereError = 100.0f, capsuleError = 100.0f;
if ( boxMaxError > 0 )
{
primFitter.fitBox( result.mVcount, result.mVertices );
boxError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getBoxVolume() ) );
}
if ( sphereMaxError > 0 )
{
primFitter.fitSphere( result.mVcount, result.mVertices );
sphereError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getSphereVolume() ) );
}
if ( capsuleMaxError > 0 )
{
primFitter.fitCapsule( result.mVcount, result.mVertices );
capsuleError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getCapsuleVolume() ) );
}
// Use the primitive type with smallest error less than the respective
// max error, or Hull if none
F32 minError = FLT_MAX;
if ( ( boxError < boxMaxError ) && ( boxError < minError ) )
{
meshType = MeshFit::Box;
minError = boxError;
}
if ( ( sphereError < sphereMaxError ) && ( sphereError < minError ) )
{
meshType = MeshFit::Sphere;
minError = sphereError;
}
if ( ( capsuleError < capsuleMaxError ) && ( capsuleError < minError ) )
{
meshType = MeshFit::Capsule;
minError = capsuleError;
}
if ( meshType == MeshFit::Box )
addBox( primFitter.mBoxSides, primFitter.mBoxTransform );
else if ( meshType == MeshFit::Sphere )
addSphere( primFitter.mSphereRadius, primFitter.mSphereCenter );
else if ( meshType == MeshFit::Capsule )
addCapsule( primFitter.mCapRadius, primFitter.mCapHeight, primFitter.mCapTransform );
// else fall through to Hull processing
}
if ( meshType == MeshFit::Hull )
{
// Create TSMesh from convex hull
mMeshes.increment();
mMeshes.last().type = MeshFit::Hull;
mMeshes.last().transform.identity();
mMeshes.last().tsmesh = createTriMesh( result.mVertices, result.mVcount, result.mIndices, result.mTcount );
mMeshes.last().tsmesh->computeBounds();
}
}
CONVEX_DECOMPOSITION::releaseConvexDecomposition( ic );
}
//-----------------------------------------------------------------------------
DefineTSShapeConstructorMethod( addPrimitive, bool, ( const char* meshName, const char* type, const char* params, TransformF txfm, const char* nodeName ),,
( meshName, type, params, txfm, nodeName ), false,
"Add a new mesh primitive to the shape.\n"
"@param meshName full name (object name + detail size) of the new mesh. If "
"no detail size is present at the end of the name, a value of 2 is used.<br>"
"An underscore before the number at the end of the name will be interpreted as "
"a negative sign. eg. \"MyMesh_4\" will be interpreted as \"MyMesh-4\".\n"
"@param type one of: \"box\", \"sphere\", \"capsule\"\n"
"@param params mesh primitive parameters:\n"
"<ul>"
"<li>for box: \"size_x size_y size_z\"</li>"
"<li>for sphere: \"radius\"</li>"
"<li>for capsule: \"height radius\"</li>"
"</ul>"
"</ul>\n"
"@param txfm local transform offset from the node for this mesh\n"
"@param nodeName name of the node to attach the new mesh to (will change the "
"object's node if adding a new mesh to an existing object)\n"
"@return true if successful, false otherwise\n\n"
"@tsexample\n"
"%this.addMesh( \"Box4\", \"box\", \"2 4 2\", \"0 2 0 0 0 1 0\", \"eye\" );\n"
"%this.addMesh( \"Sphere256\", \"sphere\", \"2\", \"0 0 0 0 0 1 0\", \"root\" );\n"
"%this.addMesh( \"MyCapsule-1\", \"capsule\", \"2 5\", \"0 0 2 0 0 1 0\", \"base01\" );\n"
"@endtsexample\n" )
{
MeshFit fit( mShape );
if ( !dStricmp( type, "box" ) )
{
// Parse box parameters
Point3F sides;
if ( dSscanf( params, "%g %g %g", &sides.x, &sides.y, &sides.z ) == 3 )
{
fit.addBox( sides, MatrixF::Identity );
fit.setReady();
}
}
else if ( !dStricmp( type, "sphere" ) )
{
// Parse sphere parameters
F32 radius;
if ( dSscanf( params, "%g", &radius ) == 1)
{
fit.addSphere( radius, Point3F::Zero );
fit.setReady();
}
}
else if ( !dStricmp( type, "capsule" ) )
{
// Parse capsule parameters
F32 radius, height;
if ( dSscanf( params, "%g %g", &radius, &height ) == 1)
{
fit.addCapsule( radius, height, MatrixF::Identity );
fit.setReady();
}
}
if ( !fit.isReady() )
{
Con::errorf( "TSShapeConstructor::addPrimitive: Invalid params: '%s' for type '%s'",
params, type );
return false;
}
TSMesh* mesh = fit.getMesh( 0 )->tsmesh;
MatrixF mat( txfm.getMatrix() );
// Transform the mesh vertices
if ( mesh->mVertexData.isReady() )
{
for (S32 i = 0; i < mesh->mVertexData.size(); i++)
{
Point3F v;
mat.mulP( mesh->mVertexData[i].vert(), &v );
mesh->mVertexData[i].vert( v );
}
}
else
{
for (S32 i = 0; i < mesh->verts.size(); i++)
{
Point3F v(mesh->verts[i]);
mat.mulP( v, &mesh->verts[i] );
}
}
// Add the mesh to the shape at the right node
mShape->addMesh( mesh, meshName );
S32 dummy;
String objName = String::GetTrailingNumber( meshName, dummy );
setObjectNode( objName, nodeName );
mShape->init();
ADD_TO_CHANGE_SET();
return true;
}}
DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 merge, F32 concavity, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( 4, 30, 30, 32, 0, 0, 0 ),
( size, type, target, depth, merge, concavity, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false,
"Autofit a mesh primitive or set of convex hulls to the shape geometry. Hulls "
"may optionally be converted to boxes, spheres and/or capsules based on their "
"volume.\n"
"@param size size for this detail level\n"
"@param type one of: box, sphere, capsule, 10-dop x, 10-dop y, 10-dop z, 18-dop, "
"26-dop, convex hulls. See the Shape Editor documentation for more details "
"about these types.\n"
"@param target geometry to fit collision mesh(es) to; either \"bounds\" (for the "
"whole shape), or the name of an object in the shape\n"
"@param depth maximum split recursion depth (hulls only)\n"
"@param merge volume % threshold used to merge hulls together (hulls only)\n"
"@param concavity volume % threshold used to detect concavity (hulls only)\n"
"@param maxVerts maximum number of vertices per hull (hulls only)\n"
"@param boxMaxError max % volume difference for a hull to be converted to a "
"box (hulls only)\n"
"@param sphereMaxError max % volume difference for a hull to be converted to "
"a sphere (hulls only)\n"
"@param capsuleMaxError max % volume difference for a hull to be converted to "
"a capsule (hulls only)\n"
"@return true if successful, false otherwise\n\n"
"@tsexample\n"
"%this.addCollisionDetail( -1, \"box\", \"bounds\" );\n"
"%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\", 4, 30, 30, 32, 0, 0, 0 );\n"
"%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\", 4, 30, 30, 32, 50, 50, 50 );\n"
"@endtsexample\n" )
{
MeshFit fit( mShape );
fit.initSourceGeometry( target );
if ( !fit.isReady() )
{
Con::errorf( "TSShapeConstructor::addCollisionDetail: Failed to initialise mesh fitter "
"using target: %s", target );
return false;
}
if ( !dStricmp( type, "box" ) )
fit.fitOBB();
else if ( !dStricmp( type, "sphere" ) )
fit.fitSphere();
else if ( !dStricmp( type, "capsule" ) )
fit.fitCapsule();
else if ( !dStricmp( type, "10-dop x" ) )
fit.fit10_DOP_X();
else if ( !dStricmp( type, "10-dop y" ) )
fit.fit10_DOP_Y();
else if ( !dStricmp( type, "10-dop z" ) )
fit.fit10_DOP_Z();
else if ( !dStricmp( type, "18-dop" ) )
fit.fit18_DOP();
else if ( !dStricmp( type, "26-dop" ) )
fit.fit26_DOP();
else if ( !dStricmp( type, "convex hulls" ) )
{
fit.fitConvexHulls( depth, merge, concavity, maxVerts,
boxMaxError, sphereMaxError, capsuleMaxError );
}
else
{
Con::errorf( "TSShape::addCollisionDetail: Invalid type: '%s'", type );
return false;
}
// Now add the fitted meshes to the shape:
// - primitives (box, sphere, capsule) need their own node (with appropriate
// transform set) so that we can use the mesh bounds to compute the real
// collision primitive at load time without having to examine the geometry.
// - convex meshes may be added at the default node, with identity transform
// - since all meshes are in the same detail level, they all get a unique
// object name
const String colNodeName( String::ToString( "Col%d", size ) );
// Add the default node with identity transform
S32 nodeIndex = mShape->findNode( colNodeName );
if ( nodeIndex == -1 )
{
addNode( colNodeName, "" );
}
else
{
MatrixF mat;
mShape->getNodeWorldTransform( nodeIndex, &mat );
if ( !mat.isIdentity() )
setNodeTransform( colNodeName, TransformF::Identity );
}
// Add the meshes to the shape =>
for ( S32 i = 0; i < fit.getMeshCount(); i++ )
{
MeshFit::Mesh* mesh = fit.getMesh( i );
// Determine a unique name for this mesh
String objName;
switch ( mesh->type )
{
case MeshFit::Box: objName = "ColBox"; break;
case MeshFit::Sphere: objName = "ColSphere"; break;
case MeshFit::Capsule: objName = "ColCapsule"; break;
default: objName = "ColConvex"; break;
}
for ( S32 suffix = i; suffix != 0; suffix /= 26 )
objName += ('A' + ( suffix % 26 ) );
String meshName = objName + String::ToString( "%d", size );
mShape->addMesh( mesh->tsmesh, meshName );
// Add a node for this object if needed (non-identity transform)
if ( mesh->transform.isIdentity() )
{
mShape->setObjectNode( objName, colNodeName );
}
else
{
addNode( meshName, colNodeName, TransformF( mesh->transform ) );
mShape->setObjectNode( objName, meshName );
}
}
mShape->init();
ADD_TO_CHANGE_SET();
return true;
}}

View file

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsMesh.h"
#include "ts/tsMeshIntrinsics.h"
#include "ts/arch/tsMeshIntrinsics.arch.h"
#include "core/module.h"
void (*zero_vert_normal_bulk)(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride) = NULL;
void (*m_matF_x_BatchedVertWeightList)(const MatrixF &mat, const dsize_t count, const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch, U8 * const __restrict outPtr, const dsize_t outStride) = NULL;
//------------------------------------------------------------------------------
// Default C++ Implementations (pretty slow)
//------------------------------------------------------------------------------
void zero_vert_normal_bulk_C(const dsize_t count, U8 * __restrict const outPtr, const dsize_t outStride)
{
register char *outData = reinterpret_cast<char *>(outPtr);
// TODO: Try prefetch w/ ptr de-reference
for(register int i = 0; i < count; i++)
{
TSMesh::__TSMeshVertexBase *outElem = reinterpret_cast<TSMesh::__TSMeshVertexBase *>(outData);
outElem->_vert.zero();
outElem->_normal.zero();
outData += outStride;
}
}
//------------------------------------------------------------------------------
void m_matF_x_BatchedVertWeightList_C(const MatrixF &mat,
const dsize_t count,
const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch,
U8 * const __restrict outPtr,
const dsize_t outStride)
{
const register MatrixF m = mat;
register Point3F tempPt;
register Point3F tempNrm;
for(register int i = 0; i < count; i++)
{
const TSSkinMesh::BatchData::BatchedVertWeight &inElem = batch[i];
TSMesh::__TSMeshVertexBase *outElem = reinterpret_cast<TSMesh::__TSMeshVertexBase *>(outPtr + inElem.vidx * outStride);
m.mulP( inElem.vert, &tempPt );
m.mulV( inElem.normal, &tempNrm );
outElem->_vert += ( tempPt * inElem.weight );
outElem->_normal += ( tempNrm * inElem.weight );
}
}
//------------------------------------------------------------------------------
// Initializer.
//------------------------------------------------------------------------------
MODULE_BEGIN( TSMeshIntrinsics )
MODULE_INIT_AFTER( 3D )
MODULE_INIT
{
// Assign defaults (C++ versions)
zero_vert_normal_bulk = zero_vert_normal_bulk_C;
m_matF_x_BatchedVertWeightList = m_matF_x_BatchedVertWeightList_C;
#if defined(TORQUE_OS_XENON)
zero_vert_normal_bulk = zero_vert_normal_bulk_X360;
m_matF_x_BatchedVertWeightList = m_matF_x_BatchedVertWeightList_X360;
#else
// Find the best implementation for the current CPU
if(Platform::SystemInfo.processor.properties & CPU_PROP_SSE)
{
#if defined(TORQUE_CPU_X86)
zero_vert_normal_bulk = zero_vert_normal_bulk_SSE;
m_matF_x_BatchedVertWeightList = m_matF_x_BatchedVertWeightList_SSE;
/* This code still has a bug left in it
#if (_MSC_VER >= 1500)
if(Platform::SystemInfo.processor.properties & CPU_PROP_SSE4_1)
m_matF_x_BatchedVertWeightList = m_matF_x_BatchedVertWeightList_SSE4;
#endif
*/
#endif
}
else if(Platform::SystemInfo.processor.properties & CPU_PROP_ALTIVEC)
{
#if !defined(TORQUE_OS_XENON) && defined(TORQUE_CPU_PPC)
zero_vert_normal_bulk = zero_vert_normal_bulk_gccvec;
m_matF_x_BatchedVertWeightList = m_matF_x_BatchedVertWeightList_gccvec;
#endif
}
#endif
}
MODULE_END;

View file

@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------
// 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 _TSMESHINTRINSICS_H_
#define _TSMESHINTRINSICS_H_
/// This is the batch-by-transform skin loop
///
/// @param mat Bone transform
/// @param count Number of input elements in the batch
/// @param batch Pointer to the first element in an aligned array of input elements
/// @param outPtr Pointer to index 0 of a TSMesh aligned vertex buffer
/// @param outStride Size, in bytes, of one entry in the vertex buffer
extern void (*m_matF_x_BatchedVertWeightList)
(const MatrixF &mat,
const dsize_t count,
const TSSkinMesh::BatchData::BatchedVertWeight * __restrict batch,
U8 * const __restrict outPtr,
const dsize_t outStride);
/// Set the vertex position and normal to (0, 0, 0)
///
/// @param count Number of elements
/// @param outPtr Pointer to a TSMesh aligned vertex buffer
/// @param outStride Size, in bytes, of one entry in the vertex buffer
extern void (*zero_vert_normal_bulk)
(const dsize_t count,
U8 * __restrict const outPtr,
const dsize_t outStride);
#endif

View file

@ -0,0 +1,371 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsPartInstance.h"
#include "math/mMath.h"
//-------------------------------------------------------------------------------------
// Constructors
//-------------------------------------------------------------------------------------
MRandomR250 TSPartInstance::smRandom;
TSPartInstance::TSPartInstance(TSShapeInstance * sourceShape)
{
VECTOR_SET_ASSOCIATION(mMeshObjects);
init(sourceShape);
}
TSPartInstance::TSPartInstance(TSShapeInstance * sourceShape, S32 objectIndex)
{
init(sourceShape);
addObject(objectIndex);
}
void TSPartInstance::init(TSShapeInstance * sourceShape)
{
mSourceShape = sourceShape;
mSizeCutoffs = NULL;
mPolyCount = NULL;
mNumDetails = 0;
mCurrentObjectDetail = 0;
mCurrentIntraDL = 1.0f;
mData = 0;
}
TSPartInstance::~TSPartInstance()
{
delete [] mPolyCount;
}
//-------------------------------------------------------------------------------------
// Methods for updating PartInstances
//-------------------------------------------------------------------------------------
void TSPartInstance::addObject(S32 objectIndex)
{
if (mSourceShape->mMeshObjects[objectIndex].forceHidden ||
mSourceShape->mMeshObjects[objectIndex].visible < 0.01f)
// not visible, don't bother
return;
mMeshObjects.push_back(&mSourceShape->mMeshObjects[objectIndex]);
}
void TSPartInstance::updateBounds()
{
// run through meshes and brute force it?
Box3F bounds;
mBounds.minExtents.set( 10E30f, 10E30f, 10E30f);
mBounds.maxExtents.set(-10E30f,-10E30f,-10E30f);
for (S32 i=0; i<mMeshObjects.size(); i++)
{
if (mMeshObjects[i]->getMesh(0))
mMeshObjects[i]->getMesh(0)->computeBounds(mMeshObjects[i]->getTransform(),bounds,mMeshObjects[i]->frame);
mBounds.minExtents.setMin(bounds.minExtents);
mBounds.maxExtents.setMax(bounds.maxExtents);
}
mCenter = mBounds.minExtents + mBounds.maxExtents;
mCenter *= 0.5f;
Point3F r = mBounds.maxExtents-mCenter;
mRadius = mSqrt(mDot(r,r));
}
//-------------------------------------------------------------------------------------
// Methods for breaking shapes into pieces
//-------------------------------------------------------------------------------------
void TSPartInstance::breakShape(TSShapeInstance * shape, S32 subShape, Vector<TSPartInstance*> & partList, F32 * probShatter, F32 * probBreak, S32 probDepth)
{
AssertFatal(subShape>=0 && subShape<shape->mShape->subShapeFirstNode.size(),"TSPartInstance::breakShape: subShape out of range.");
S32 start = shape->mShape->subShapeFirstNode[subShape];
TSPartInstance::breakShape(shape, NULL, start, partList, probShatter, probBreak, probDepth);
// update bounds (and get rid of empty parts)
for (S32 i=0; i<partList.size(); i++)
{
if (partList[i]->mMeshObjects.size())
partList[i]->updateBounds();
else
{
partList.erase(i);
i--;
}
}
}
void TSPartInstance::breakShape(TSShapeInstance * shape, TSPartInstance * currentPart, S32 currentNode, Vector<TSPartInstance*> & partList, F32 * probShatter, F32 * probBreak, S32 probDepth)
{
AssertFatal( !probDepth || (probShatter && probBreak),"TSPartInstance::breakShape: probabilities improperly specified.");
const TSShape::Node * node = &shape->mShape->nodes[currentNode];
S32 object = node->firstObject;
S32 child = node->firstChild;
// copy off probabilities and update probability lists for next level
F32 ps = probShatter ? *probShatter : 1.0f;
F32 pb = probBreak ? *probBreak : 1.0f;
if (probDepth>1 && probShatter && probBreak)
{
probShatter++;
probBreak++;
probDepth--;
}
// what to do...depending on how the die roll, we can:
// a) shatter the shape at this level -- meaning we make a part out of each object on this node and
// we make parts out of all the children (perhaps breaking them up further still)
// b) break the shape off at this level -- meaning we make a part out of the intact piece from here
// on down (again, we might break the result further as we iterate through the nodes...what breaking
// the shape really does is separate this piece from the parent piece).
// c) add this piece to the parent -- meaning all objects on this node are added to the parent, and children
// are also added (but children will be recursively sent through this routine, so if a parent gets option
// (c) and the child option (a) or (b), then the child will be ripped from the parents grasp. Cruel
// people us coders are.
// Note: (a) is the only way that two objects on the same node can be separated...that is why both
// option a and option b are needed.
if (!probShatter || smRandom.randF() < ps)
{
// option a -- shatter the shape at this level
// iterate through the objects, make part out of each one
while (object>=0)
{
partList.increment();
partList.last() = new TSPartInstance(shape,object);
object = shape->mShape->objects[object].nextSibling;
}
// iterate through the child nodes, call ourselves on each one with currentPart = NULL
while (child>=0)
{
TSPartInstance::breakShape(shape,NULL,child,partList,probShatter,probBreak,probDepth);
child = shape->mShape->nodes[child].nextSibling;
}
return;
}
if (!probBreak || smRandom.randF() < pb)
// option b -- break the shape off at this level
currentPart = NULL; // fall through to option C
// option c -- add this piece to the parent
if (!currentPart)
{
currentPart = new TSPartInstance(shape);
partList.push_back(currentPart);
}
// iterate through objects, add to currentPart
while (object>=0)
{
currentPart->addObject(object);
object = shape->mShape->objects[object].nextSibling;
}
// iterate through child nodes, call ourselves on each one with currentPart as is
while (child>=0)
{
TSPartInstance::breakShape(shape,currentPart,child,partList,probShatter,probBreak,probDepth);
child = shape->mShape->nodes[child].nextSibling;
}
}
//-------------------------------------------------------------------------------------
// render methods -- we use TSShapeInstance code as much as possible
// issues: setupTexturing expects a detail level, we give it an object detail level
//-------------------------------------------------------------------------------------
void TSPartInstance::render(S32 od, const TSRenderState &rdata)
{
S32 i;
// render mesh objects
for (i=0; i<mMeshObjects.size(); i++)
mMeshObjects[i]->render(od,mSourceShape->getMaterialList(),rdata,1.0);
}
//-------------------------------------------------------------------------------------
// Detail selection
// 2 methods:
// method 1: use source shapes detail levels...
// method 2: pass in our own table...
// In either case, you can compute the pixel size on your own or let open gl do it.
// If you want to use method 2, you have to call setDetailData sometime before selecting detail
//-------------------------------------------------------------------------------------
void TSPartInstance::setDetailData(F32 * sizeCutoffs, S32 numDetails)
{
if (mSizeCutoffs == sizeCutoffs && mNumDetails==numDetails)
return;
mSizeCutoffs = sizeCutoffs;
mNumDetails = numDetails;
delete [] mPolyCount;
mPolyCount = NULL;
}
/*
void TSPartInstance::selectCurrentDetail(bool ignoreScale)
{
if (mSizeCutoffs)
{
selectCurrentDetail(mSizeCutoffs,mNumDetails,ignoreScale);
return;
}
mSourceShape->selectCurrentDetail(ignoreScale);
mCurrentObjectDetail = mSourceShape->getCurrentDetail();
mCurrentIntraDL = mSourceShape->getCurrentIntraDetail();
}
void TSPartInstance::selectCurrentDetail(F32 pixelSize)
{
if (mSizeCutoffs)
{
selectCurrentDetail(pixelSize,mSizeCutoffs,mNumDetails);
return;
}
mSourceShape->selectCurrentDetail(pixelSize);
mCurrentObjectDetail = mSourceShape->getCurrentDetail();
mCurrentIntraDL = mSourceShape->getCurrentIntraDetail();
}
void TSPartInstance::selectCurrentDetail(F32 dist, F32 invScale)
{
if (mSizeCutoffs)
{
const RectI &viewport = GFX->getViewport();
F32 pixelScale = viewport.extent.x * 1.6f / 640.0f;
F32 pixelSize = GFX->projectRadius(dist*invScale,mSourceShape->getShape()->radius) * pixelScale * TSShapeInstance::smDetailAdjust;
selectCurrentDetail(pixelSize,mSizeCutoffs,mNumDetails);
return;
}
mSourceShape->selectCurrentDetail(dist, invScale);
mCurrentObjectDetail = mSourceShape->getCurrentDetail();
mCurrentIntraDL = mSourceShape->getCurrentIntraDetail();
}
void TSPartInstance::selectCurrentDetail(F32 * sizeCutoffs, S32 numDetails, bool ignoreScale)
{
// compute pixel size
Point3F p;
MatrixF toCam = GFX->getWorldMatrix();
toCam.mulP(mCenter,&p);
F32 dist = mDot(p,p);
F32 scale = 1.0f;
if (!ignoreScale)
{
// any scale?
Point3F x,y,z;
toCam.getRow(0,&x);
toCam.getRow(1,&y);
toCam.getRow(2,&z);
F32 scalex = mDot(x,x);
F32 scaley = mDot(y,y);
F32 scalez = mDot(z,z);
scale = scalex;
if (scaley > scale)
scale = scaley;
if (scalez > scale)
scale = scalez;
}
dist /= scale;
dist = mSqrt(dist);
const RectI &viewport = GFX->getViewport();
// JMQMERGE: is this using a hardcoded res/aspect ? (and the code above)
F32 pixelScale = viewport.extent.x * 1.6f / 640.0f;
F32 pixelRadius = GFX->projectRadius(dist,mRadius) * pixelScale * TSShapeInstance::smDetailAdjust;
selectCurrentDetail(pixelRadius,sizeCutoffs,numDetails);
}
void TSPartInstance::selectCurrentDetail(F32 pixelSize, F32 * sizeCutoffs, S32 numDetails)
{
mCurrentObjectDetail = 0;
while (numDetails)
{
if (pixelSize > *sizeCutoffs)
return;
mCurrentObjectDetail++;
numDetails--;
sizeCutoffs++;
}
mCurrentObjectDetail = -1;
}
*/
//-------------------------------------------------------------------------------------
// Detail query methods...complicated because there are two ways that detail information
// can be determined...1) using source shape, or 2) using mSizeCutoffs
//-------------------------------------------------------------------------------------
F32 TSPartInstance::getDetailSize(S32 dl) const
{
if (dl<0)
return 0;
else if (mSizeCutoffs && dl<mNumDetails)
return mSizeCutoffs[dl];
else if (!mSizeCutoffs && dl<=mSourceShape->getShape()->mSmallestVisibleDL)
return mSourceShape->getShape()->details[dl].size;
else return 0;
}
S32 TSPartInstance::getPolyCount(S32 dl)
{
if (!mPolyCount)
computePolyCount();
if (dl<0 || dl>=mNumDetails)
return 0;
else
return mPolyCount[dl];
}
void TSPartInstance::computePolyCount()
{
if (!mSizeCutoffs)
mNumDetails = mSourceShape->getShape()->mSmallestVisibleDL+1;
delete [] mPolyCount;
mPolyCount = new S32[mNumDetails];
for (S32 i=0; i<mNumDetails; i++)
{
mPolyCount[i] = 0;
for (S32 j=0; j<mMeshObjects.size(); j++)
{
if (mMeshObjects[j]->getMesh(i))
mPolyCount[i] += mMeshObjects[j]->getMesh(i)->getNumPolys();
}
}
}

View file

@ -0,0 +1,138 @@
//-----------------------------------------------------------------------------
// 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 _TSPARTINSTANCE_H_
#define _TSPARTINSTANCE_H_
#ifndef _TSSHAPEINSTANCE_H_
#include "ts/tsShapeInstance.h"
#endif
class TSPartInstance
{
/// TSPartInstance assumes ownership (or shared ownership) of the source shape. This means that the source
/// shape cannot be deleted so long as the part instance is still around. This also means that any change
/// to source shapes's transforms or other animation properties will affect how the part instance displays.
/// It is ok (even expected), however, to have many part instances accessing the same shape.
TSShapeInstance * mSourceShape;
/// @name Bounding info
/// @{
Box3F mBounds;
Point3F mCenter;
F32 mRadius;
/// @}
/// detail selection uses the table pointed to by this member
///
/// if this member is blank, then it uses source shape to determine detail...
///
/// detail 0 draws up until size of shape is less than mSizeCutoffs[0], detail 1 until mSizeCutoffs[1], etc.
F32 * mSizeCutoffs;
S32 * mPolyCount;
S32 mNumDetails;
/// @name Detail Levels
/// detail levels on part instance correspond directly
/// to object details on objects -- this is different
/// from shape instances where dl corresponds to a
/// subtree number and object detail. The reason
/// for this is that partinstances are derived from
/// a single subtree of a shape instance, so the subtree
/// is implied (implied by which objects are in the part instance)...
/// @{
S32 mCurrentObjectDetail;
F32 mCurrentIntraDL;
/// @}
Vector<TSShapeInstance::MeshObjectInstance*> mMeshObjects;
static MRandomR250 smRandom;
void addObject(S32 objectIndex);
void updateBounds();
void renderDetailMap(S32 od);
void renderEnvironmentMap(S32 od);
void renderFog(S32 od);
void init(TSShapeInstance *);
static void breakShape(TSShapeInstance *, TSPartInstance *, S32 currentNode,
Vector<TSPartInstance*> & partList, F32 * probShatter,
F32 * probBreak, S32 probDepth);
/// @name Private Detail Selection Methods
/// @{
void selectCurrentDetail(F32 * sizeCutoffs, S32 numDetails, bool ignoreScale);
void selectCurrentDetail(F32 pixelSize, F32 * sizeCutoffs, S32 numDetails);
void computePolyCount();
/// @}
public:
TSPartInstance(TSShapeInstance * source);
TSPartInstance(TSShapeInstance * source, S32 objectIndex);
~TSPartInstance();
const TSShape * getShape() { return mSourceShape->getShape(); }
TSShapeInstance * getSourceShapeInstance(){ return mSourceShape; }
static void breakShape(TSShapeInstance *, S32 subShape, Vector<TSPartInstance*> & partList, F32 * probShatter, F32 * probBreak, S32 probDepth);
Point3F & getCenter() { return mCenter; }
Box3F & getBounds() { return mBounds; }
F32 & getRadius() { return mRadius; }
void render( const TSRenderState &rdata ) { render( mCurrentObjectDetail, rdata ); }
void render( S32 dl, const TSRenderState &rdata );
/// choose detail method -- pass in NULL for first parameter to just use shapes data
void setDetailData(F32 * sizeCutoffs, S32 numDetails);
/// @name Detail Selection
/// @{
/*
void selectCurrentDetail(bool ignoreScale = false);
void selectCurrentDetail(F32 pixelSize);
void selectCurrentDetail2(F32 adjustedDist);
*/
/// @}
/// @name Detail Information Accessors
/// @{
F32 getDetailSize( S32 dl ) const;
S32 getPolyCount( S32 dl );
S32 getNumDetails() const { return mSizeCutoffs ? mNumDetails : mSourceShape->getShape()->mSmallestVisibleDL+1; }
S32 getCurrentObjectDetail() const { return mCurrentObjectDetail; }
void setCurrentObjectDetail(S32 od) { mCurrentObjectDetail = od; }
F32 getCurrentIntraDetail() const { return mCurrentIntraDL; }
void setCurrentIntraDetail(F32 intra) { mCurrentIntraDL = intra; }
/// @}
void *mData; ///< for use by app
};
#endif

View file

@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------
// 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/tsRenderState.h"
TSRenderState::TSRenderState()
: mState( NULL ),
mCubemap( NULL ),
mFadeOverride( 1.0f ),
mNoRenderTranslucent( false ),
mNoRenderNonTranslucent( false ),
mMaterialHint( NULL ),
mCuller( NULL ),
mLightQuery( NULL ),
mUseOriginSort( false )
{
}
TSRenderState::TSRenderState( const TSRenderState &state )
: mState( state.mState ),
mCubemap( state.mCubemap ),
mFadeOverride( state.mFadeOverride ),
mNoRenderTranslucent( state.mNoRenderTranslucent ),
mNoRenderNonTranslucent( state.mNoRenderNonTranslucent ),
mMaterialHint( state.mMaterialHint ),
mCuller( state.mCuller ),
mLightQuery( state.mLightQuery ),
mUseOriginSort( state.mUseOriginSort )
{
}

View file

@ -0,0 +1,153 @@
//-----------------------------------------------------------------------------
// 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 _TSRENDERDATA_H_
#define _TSRENDERDATA_H_
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
class SceneRenderState;
class GFXCubemap;
class Frustum;
class LightQuery;
/// A simple class for passing render state through the pre-render pipeline.
///
/// @section TSRenderState_intro Introduction
///
/// TSRenderState holds on to certain pieces of data that may be
/// set at the preparation stage of rendering (prepRengerImage etc.)
/// which are needed further along in the process of submitting
/// a render instance for later rendering by the RenderManager.
///
/// It was created to clean up and refactor the DTS rendering
/// from having a large number of static data that would be used
/// in varying places. These statics were confusing and would often
/// cause problems when not properly cleaned up by various objects after
/// submitting their RenderInstances.
///
/// @section TSRenderState_functionality What Does TSRenderState Do?
///
/// TSRenderState is a simple class that performs the function of passing along
/// (from the prep function(s) to the actual submission) the data
/// needed for the desired state of rendering.
///
/// @section TSRenderState_example Usage Example
///
/// TSRenderState is very easy to use. Merely create a TSRenderState object (in prepRenderImage usually)
/// and set any of the desired data members (SceneRenderState, camera transform etc.), and pass the address of
/// your TSRenderState to your render function.
///
class TSRenderState
{
protected:
const SceneRenderState *mState;
GFXCubemap *mCubemap;
/// Used to override the normal
/// fade value of an object.
/// This is multiplied by the current
/// fade value of the instance
/// to gain the resulting visibility fade (see TSMesh::render()).
F32 mFadeOverride;
/// These are used in some places
/// TSShapeInstance::render, however,
/// it appears they are never set to anything
/// other than false. We provide methods
/// for setting them regardless.
bool mNoRenderTranslucent;
bool mNoRenderNonTranslucent;
/// A generic hint value passed from the game
/// code down to the material for use by shader
/// features.
void *mMaterialHint;
/// An optional object space frustum used to cull
/// subobjects within the shape.
const Frustum *mCuller;
/// Use the origin point of the mesh for distance
/// sorting for transparency instead of the nearest
/// bounding box point.
bool mUseOriginSort;
/// The lighting query object used if any materials
/// are forward lit and need lights.
LightQuery *mLightQuery;
public:
TSRenderState();
TSRenderState( const TSRenderState &state );
/// @name Get/Set methods.
/// @{
///@see mState
const SceneRenderState* getSceneState() const { return mState; }
void setSceneState( const SceneRenderState *state ) { mState = state; }
///@see mCubemap
GFXCubemap* getCubemap() const { return mCubemap; }
void setCubemap( GFXCubemap *cubemap ) { mCubemap = cubemap; }
///@see mFadeOverride
F32 getFadeOverride() const { return mFadeOverride; }
void setFadeOverride( F32 fade ) { mFadeOverride = fade; }
///@see mNoRenderTranslucent
bool isNoRenderTranslucent() const { return mNoRenderTranslucent; }
void setNoRenderTranslucent( bool noRenderTrans ) { mNoRenderTranslucent = noRenderTrans; }
///@see mNoRenderNonTranslucent
bool isNoRenderNonTranslucent() const { return mNoRenderNonTranslucent; }
void setNoRenderNonTranslucent( bool noRenderNonTrans ) { mNoRenderNonTranslucent = noRenderNonTrans; }
///@see mMaterialHint
void* getMaterialHint() const { return mMaterialHint; }
void setMaterialHint( void *materialHint ) { mMaterialHint = materialHint; }
///@see mCuller
const Frustum* getCuller() const { return mCuller; }
void setCuller( const Frustum *culler ) { mCuller = culler; }
///@see mUseOriginSort
void setOriginSort( bool enable ) { mUseOriginSort = enable; }
bool useOriginSort() const { return mUseOriginSort; }
///@see mLightQuery
void setLightQuery( LightQuery *query ) { mLightQuery = query; }
LightQuery* getLightQuery() const { return mLightQuery; }
/// @}
};
#endif // _TSRENDERDATA_H_

2289
Engine/source/ts/tsShape.cpp Normal file

File diff suppressed because it is too large Load diff

692
Engine/source/ts/tsShape.h Normal file
View file

@ -0,0 +1,692 @@
//-----------------------------------------------------------------------------
// 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 _TSSHAPE_H_
#define _TSSHAPE_H_
#ifndef _TSMESH_H_
#include "ts/tsMesh.h"
#endif
#ifndef _TSINTEGERSET_H_
#include "ts/tsIntegerSet.h"
#endif
#ifndef _TSTRANSFORM_H_
#include "ts/tsTransform.h"
#endif
#ifndef _TSSHAPEALLOC_H_
#include "ts/tsShapeAlloc.h"
#endif
#define DTS_EXPORTER_CURRENT_VERSION 124
class TSMaterialList;
class TSLastDetail;
class PhysicsCollision;
//
struct CollisionShapeInfo
{
S32 colNode;
PhysicsCollision *colShape;
};
/// TSShape stores generic data for a 3space model.
///
/// TSShape and TSShapeInstance act in conjunction to allow the rendering and
/// manipulation of a three dimensional model.
///
/// @note The material lists are the only thing that is not loaded in TSShape.
/// instead, they are loaded in TSShapeInstance because of a former restriction
/// on the resource manager where only one file could be opened at a time.
/// The resource manager restriction has been resolved, but the material
/// lists are still loaded in TSShapeInstance.
///
/// @see TSShapeInstance for a further discussion of the 3space system.
class TSShape
{
public:
enum
{
UniformScale = BIT(0),
AlignedScale = BIT(1),
ArbitraryScale = BIT(2),
Blend = BIT(3),
Cyclic = BIT(4),
MakePath = BIT(5),
HasTranslucency= BIT(6),
AnyScale = UniformScale | AlignedScale | ArbitraryScale
};
/// Nodes hold the transforms in the shape's tree. They are the bones of the skeleton.
struct Node
{
S32 nameIndex;
S32 parentIndex;
// computed at runtime
S32 firstObject;
S32 firstChild;
S32 nextSibling;
};
/// Objects hold renderable items (in particular meshes).
///
/// Each object has a number of meshes associated with it.
/// Each mesh corresponds to a different detail level.
///
/// meshIndicesIndex points to numMeshes consecutive indices
/// into the meshList and meshType vectors. It indexes the
/// meshIndexList vector (meshIndexList is merely a clearinghouse
/// for the object's mesh lists). Some indices may correspond to
/// no mesh -- which means no mesh will be drawn for the part for
/// the given detail level. See comments on the meshIndexList
/// for how null meshes are coded.
///
/// @note Things are stored this way so that there are no pointers.
/// This makes serialization to disk dramatically simpler.
struct Object
{
S32 nameIndex;
S32 numMeshes;
S32 startMeshIndex; ///< Index into meshes array.
S32 nodeIndex;
// computed at load
S32 nextSibling;
S32 firstDecal; // DEPRECATED
};
/// A Sequence holds all the information necessary to perform a particular animation (sequence).
///
/// Sequences index a range of keyframes. Keyframes are assumed to be equally spaced in time.
///
/// Each node and object is either a member of the sequence or not. If not, they are set to
/// default values when we switch to the sequence unless they are members of some other active sequence.
/// Blended sequences "add" a transform to the current transform of a node. Any object animation of
/// a blended sequence over-rides any existing object state. Blended sequences are always
/// applied after non-blended sequences.
struct Sequence
{
S32 nameIndex;
S32 numKeyframes;
F32 duration;
S32 baseRotation;
S32 baseTranslation;
S32 baseScale;
S32 baseObjectState;
S32 baseDecalState; // DEPRECATED
S32 firstGroundFrame;
S32 numGroundFrames;
S32 firstTrigger;
S32 numTriggers;
F32 toolBegin;
/// @name Bitsets
/// These bitsets code whether this sequence cares about certain aspects of animation
/// e.g., the rotation, translation, or scale of node transforms,
/// or the visibility, frame or material frame of objects.
/// @{
TSIntegerSet rotationMatters; ///< Set of nodes
TSIntegerSet translationMatters; ///< Set of nodes
TSIntegerSet scaleMatters; ///< Set of nodes
TSIntegerSet visMatters; ///< Set of objects
TSIntegerSet frameMatters; ///< Set of objects
TSIntegerSet matFrameMatters; ///< Set of objects
/// @}
S32 priority;
U32 flags;
U32 dirtyFlags; ///< determined at load time
/// @name Source Data
/// Store some information about where the sequence data came from (used by
/// TSShapeConstructor and the Shape Editor)
/// @{
struct SeqSourceData
{
String from; // The source sequence (ie. a DSQ file)
S32 start; // The first frame in the source sequence
S32 end; // The last frame in the source sequence
S32 total; // The total number of frames in the source sequence
String blendSeq; // The blend reference sequence
S32 blendFrame; // The blend reference frame
SeqSourceData() : from("\t"), start(0), end(0), total(0), blendSeq(""), blendFrame(0) { }
} sourceData;
/// @name Flag Tests
/// Each of these tests a different flag against the object's flag list
/// to determine the attributes of the given object.
/// @{
bool testFlags(U32 comp) const { return (flags&comp)!=0; }
bool animatesScale() const { return testFlags(AnyScale); }
bool animatesUniformScale() const { return testFlags(UniformScale); }
bool animatesAlignedScale() const { return testFlags(AlignedScale); }
bool animatesArbitraryScale() const { return testFlags(ArbitraryScale); }
bool isBlend() const { return testFlags(Blend); }
bool isCyclic() const { return testFlags(Cyclic); }
bool makePath() const { return testFlags(MakePath); }
/// @}
/// @name IO
/// @{
void read(Stream *, bool readNameIndex = true);
void write(Stream *, bool writeNameIndex = true) const;
/// @}
};
/// Describes state of an individual object. Includes everything in an object that can be
/// controlled by animation.
struct ObjectState
{
F32 vis;
S32 frameIndex;
S32 matFrameIndex;
};
/// When time on a sequence advances past a certain point, a trigger takes effect and changes
/// one of the state variables to on or off. (State variables found in TSShapeInstance::mTriggerStates)
struct Trigger
{
enum TriggerStates
{
StateOn = BIT(31),
InvertOnReverse = BIT(30),
StateMask = BIT(30)-1
};
U32 state; ///< One of TriggerStates
F32 pos;
};
/// Details are used for render detail selection.
///
/// As the projected size of the shape changes,
/// a different node structure can be used (subShape) and a different objectDetail can be selected
/// for each object drawn. Either of these two parameters can also stay constant, but presumably
/// not both. If size is negative then the detail level will never be selected by the standard
/// detail selection process. It will have to be selected by name. Such details are "utility
/// details" because they exist to hold data (node positions or collision information) but not
/// normally to be drawn. By default there will always be a "Ground" utility detail.
///
/// Note that this struct should always be 32bit aligned
/// as its required by assembleShape/disassembleShape.
struct Detail
{
S32 nameIndex;
S32 subShapeNum;
S32 objectDetailNum;
F32 size;
F32 averageError;
F32 maxError;
S32 polyCount;
/// These values are new autobillboard settings stored
/// as part of the Detail struct in version 26 and above.
/// @{
S32 bbDimension; ///< The size of the autobillboard image.
S32 bbDetailLevel; ///< The detail to render as the autobillboard.
U32 bbEquatorSteps; ///< The number of autobillboard images to capture around the equator.
U32 bbPolarSteps; ///< The number of autobillboard images to capture along the pole.
F32 bbPolarAngle; ///< The angle in radians at which the top/bottom autobillboard images should be displayed.
U32 bbIncludePoles; ///< If non-zero then top and bottom images are generated for the autobillboard.
/// @}
};
/// @name Collision Accelerators
///
/// For speeding up buildpolylist and support calls.
/// @{
struct ConvexHullAccelerator {
S32 numVerts;
Point3F* vertexList;
Point3F* normalList;
U8** emitStrings;
};
ConvexHullAccelerator* getAccelerator(S32 dl);
/// @}
/// @name Shape Vector Data
/// @{
Vector<Node> nodes;
Vector<Object> objects;
Vector<ObjectState> objectStates;
Vector<S32> subShapeFirstNode;
Vector<S32> subShapeFirstObject;
Vector<S32> detailFirstSkin;
Vector<S32> subShapeNumNodes;
Vector<S32> subShapeNumObjects;
Vector<Detail> details;
Vector<Quat16> defaultRotations;
Vector<Point3F> defaultTranslations;
/// @}
/// These are set up at load time, but memory is allocated along with loaded data
/// @{
Vector<S32> subShapeFirstTranslucentObject;
Vector<TSMesh*> meshes;
/// @}
/// @name Alpha Vectors
/// these vectors describe how to transition between detail
/// levels using alpha. "alpha-in" next detail as intraDL goes
/// from alphaIn+alphaOut to alphaOut. "alpha-out" current
/// detail level as intraDL goes from alphaOut to 0.
/// @note
/// - intraDL is at 1 when if shape were any closer to us we'd be at dl-1
/// - intraDL is at 0 when if shape were any farther away we'd be at dl+1
/// @{
Vector<F32> alphaIn;
Vector<F32> alphaOut
;
/// @}
/// @name Resizeable vectors
/// @{
Vector<Sequence> sequences;
Vector<Quat16> nodeRotations;
Vector<Point3F> nodeTranslations;
Vector<F32> nodeUniformScales;
Vector<Point3F> nodeAlignedScales;
Vector<Quat16> nodeArbitraryScaleRots;
Vector<Point3F> nodeArbitraryScaleFactors;
Vector<Quat16> groundRotations;
Vector<Point3F> groundTranslations;
Vector<Trigger> triggers;
Vector<TSLastDetail*> billboardDetails;
Vector<ConvexHullAccelerator*> detailCollisionAccelerators;
Vector<String> names;
/// @}
TSMaterialList * materialList;
/// @name Bounding
/// @{
F32 radius;
F32 tubeRadius;
Point3F center;
Box3F bounds;
/// @}
// various...
U32 mExporterVersion;
F32 mSmallestVisibleSize; ///< Computed at load time from details vector.
S32 mSmallestVisibleDL; ///< @see mSmallestVisibleSize
S32 mReadVersion; ///< File version that this shape was read from.
U32 mFlags; ///< hasTranslucancy
U32 data; ///< User-defined data storage.
/// If enabled detail selection will use the
/// legacy screen error method for lod.
/// @see setDetailFromScreenError
bool mUseDetailFromScreenError;
// TODO: This would be nice as Tuple<>
struct LodPair
{
S8 level; // -1 to 128
U8 intra; // encoded 0 to 1
inline void set( S32 dl, F32 intraDL )
{
level = (S8)dl;
intra = (S8)( intraDL * 255.0f );
}
inline void get( S32 &dl, F32 &intraDL )
{
dl = level;
intraDL = (F32)intra / 255.0f;
}
};
/// The lod lookup table where we mark down the detail
/// level and intra-detail level for each pixel size.
Vector<LodPair> mDetailLevelLookup;
/// The GFX vertex format for all detail meshes in the shape.
/// @see initVertexFeatures()
GFXVertexFormat mVertexFormat;
/// The GFX vertex size in bytes for all detail meshes in the shape.
/// @see initVertexFeatures()
U32 mVertSize;
/// Is true if this shape contains skin meshes.
bool mHasSkinMesh;
bool mSequencesConstructed;
S8* mShapeData;
U32 mShapeDataSize;
// shape class has few methods --
// just constructor/destructor, io, and lookup methods
// constructor/destructor
TSShape();
~TSShape();
void init();
void initMaterialList(); ///< you can swap in a new material list, but call this if you do
bool preloadMaterialList(const Torque::Path &path); ///< called to preload and validate the materials in the mat list
void setupBillboardDetails( const String &cachePath );
/// Called from init() to calcuate the GFX vertex features for
/// all detail meshes in the shape.
void initVertexFeatures();
bool getSequencesConstructed() const { return mSequencesConstructed; }
void setSequencesConstructed(const bool c) { mSequencesConstructed = c; }
/// @name Lookup Animation Info
/// indexed by keyframe number and offset (which object/node/decal
/// of the animated objects/nodes/decals you want information for).
/// @{
QuatF & getRotation(const Sequence & seq, S32 keyframeNum, S32 rotNum, QuatF *) const;
const Point3F & getTranslation(const Sequence & seq, S32 keyframeNum, S32 tranNum) const;
F32 getUniformScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const;
const Point3F & getAlignedScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const;
TSScale & getArbitraryScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum, TSScale *) const;
const ObjectState & getObjectState(const Sequence & seq, S32 keyframeNum, S32 objectNum) const;
/// @}
/// build LOS collision detail
void computeAccelerator(S32 dl);
bool buildConvexHull(S32 dl) const;
void computeBounds(S32 dl, Box3F & bounds) const; // uses default transforms to compute bounding box around a detail level
// see like named method on shapeInstance if you want to use animated transforms
/// Used to find collision detail meshes in the DTS.
///
/// @param useVisibleMesh If true return the highest visible detail level.
/// @param outDetails The output detail index vector.
/// @param outLOSDetails The optional output LOS detail vector.
///
void findColDetails( bool useVisibleMesh, Vector<S32> *outDetails, Vector<S32> *outLOSDetails ) const;
/// Builds a physics collision shape at the requested scale.
///
/// If using the visible mesh one or more triangle meshes are created
/// from the first visible detail level.
///
/// If using collision meshes we look for mesh names prefixed with the
/// following hints:
//
/// "colbox"
/// "colsphere"
/// "colcapsule"
/// "colmesh"
///
/// In the case of the primitives the mesh bounding box is used to generate
/// a box, sphere, or capsule collision shape. The "colmesh" will create a
/// concave triangle mesh for collision.
///
/// Any other named collision shape is interpreted as a regular convex hull.
///
/// @return The collision object or NULL if no collision data could be generated.
///
PhysicsCollision* buildColShape( bool useVisibleMesh, const Point3F &scale );
/// Like buildColShape except we build one PhysicsCollision object per
/// collision node.
///
/// Results are returned by filling in the CollisionShapeInfo Vector, which also
/// specifies the collision node index for each PhysicsCollision built.
///
void buildColShapes( bool useVisibleMesh, const Point3F &scale, Vector< CollisionShapeInfo > *list );
/// For internal use.
PhysicsCollision* _buildColShapes( bool useVisibleMesh, const Point3F &scale, Vector< CollisionShapeInfo > *list, bool perMesh );
/// @name Lookup Methods
/// @{
/// Returns index into the name vector that equals the passed name.
S32 findName( const String &name ) const;
/// Returns name string at the passed name vector index.
const String& getName( S32 nameIndex ) const;
/// Returns name string for mesh at the passed index.
const String& getMeshName( S32 meshIndex ) const;
/// Returns name string for node at the passed index.
const String& getNodeName( S32 nodeIndex ) const;
/// Returns name string for sequence at the passed index.
const String& getSequenceName( S32 seqIndex ) const;
S32 getTargetCount() const;
const String& getTargetName( S32 mapToNameIndex ) const;
S32 findNode(S32 nameIndex) const;
S32 findNode(const String &name) const { return findNode(findName(name)); }
S32 findObject(S32 nameIndex) const;
S32 findObject(const String &name) const { return findObject(findName(name)); }
S32 findDetail(S32 nameIndex) const;
S32 findDetail(const String &name) const { return findDetail(findName(name)); }
S32 findDetailBySize(S32 size) const;
S32 findSequence(S32 nameIndex) const;
S32 findSequence(const String &name) const { return findSequence(findName(name)); }
S32 getSubShapeForNode(S32 nodeIndex);
S32 getSubShapeForObject(S32 objIndex);
void getSubShapeDetails(S32 subShapeIndex, Vector<S32>& validDetails);
void getNodeWorldTransform(S32 nodeIndex, MatrixF* mat) const;
void getNodeKeyframe(S32 nodeIndex, const TSShape::Sequence& seq, S32 keyframe, MatrixF* mat) const;
void getNodeObjects(S32 nodeIndex, Vector<S32>& nodeObjects);
void getNodeChildren(S32 nodeIndex, Vector<S32>& nodeChildren);
void getObjectDetails(S32 objIndex, Vector<S32>& objDetails);
bool findMeshIndex(const String &meshName, S32& objIndex, S32& meshIndex);
TSMesh* findMesh(const String &meshName);
bool hasTranslucency() const { return (mFlags & HasTranslucency)!=0; }
const GFXVertexFormat* getVertexFormat() const { return &mVertexFormat; }
U32 getVertexSize() const { return mVertSize; }
/// @}
/// @name Alpha Transitions
/// These control default values for alpha transitions between detail levels
/// @{
static F32 smAlphaOutLastDetail;
static F32 smAlphaInBillboard;
static F32 smAlphaOutBillboard;
static F32 smAlphaInDefault;
static F32 smAlphaOutDefault;
/// @}
/// don't load this many of the highest detail levels (although we always
/// load one renderable detail if there is one)
static S32 smNumSkipLoadDetails;
/// by default we initialize shape when we read...
static bool smInitOnRead;
/// @name Version Info
/// @{
/// Most recent version...the one we write
static S32 smVersion;
/// Version currently being read, only valid during read
static S32 smReadVersion;
static const U32 smMostRecentExporterVersion;
///@}
/// @name Persist Methods
/// Methods for saving/loading shapes to/from streams
/// @{
bool canWriteOldFormat() const;
void write(Stream *, bool saveOldFormat=false);
bool read(Stream *);
void readOldShape(Stream * s, S32 * &, S16 * &, S8 * &, S32 &, S32 &, S32 &);
void writeName(Stream *, S32 nameIndex);
S32 readName(Stream *, bool addName);
/// Initializes our TSShape to be ready to receive put mesh data
void createEmptyShape();
void exportSequences(Stream *);
void exportSequence(Stream * s, const TSShape::Sequence& seq, bool saveOldFormat);
bool importSequences(Stream *, const String& sequencePath);
/// @}
/// @name Persist Helper Functions
/// @{
static TSShapeAlloc smTSAlloc;
void fixEndian(S32 *, S16 *, S8 *, S32, S32, S32);
/// @}
/// @name Memory Buffer Transfer Methods
/// uses TSShape::Alloc structure
/// @{
void assembleShape();
void disassembleShape();
///@}
/// mem buffer transfer helper (indicate when we don't want to include a particular mesh/decal)
bool checkSkip(S32 meshNum, S32 & curObject, S32 skipDL);
void fixupOldSkins(S32 numMeshes, S32 numSkins, S32 numDetails, S32 * detailFirstSkin, S32 * detailNumSkins);
/// @name Shape Editing
/// @{
S32 addName(const String& name);
bool removeName(const String& name);
void updateSmallestVisibleDL();
S32 addDetail(const String& dname, S32 size, S32 subShapeNum);
S32 addImposter( const String& cachePath,
S32 size,
S32 numEquatorSteps,
S32 numPolarSteps,
S32 dl,
S32 dim,
bool includePoles,
F32 polarAngle );
bool removeImposter();
bool renameNode(const String& oldName, const String& newName);
bool renameObject(const String& oldName, const String& newName);
bool renameDetail(const String& oldName, const String& newName);
bool renameSequence(const String& oldName, const String& newName);
bool setNodeTransform(const String& name, const Point3F& pos, const QuatF& rot);
bool addNode(const String& name, const String& parentName, const Point3F& pos, const QuatF& rot);
bool removeNode(const String& name);
S32 addObject(const String& objName, S32 subShapeIndex);
void addMeshToObject(S32 objIndex, S32 meshIndex, TSMesh* mesh);
void removeMeshFromObject(S32 objIndex, S32 meshIndex);
bool setObjectNode(const String& objName, const String& nodeName);
bool removeObject(const String& objName);
TSMesh* copyMesh( const TSMesh* srcMesh ) const;
bool addMesh(TSShape* srcShape, const String& srcMeshName, const String& meshName);
bool addMesh(TSMesh* mesh, const String& meshName);
bool setMeshSize(const String& meshName, S32 size);
bool removeMesh(const String& meshName);
S32 setDetailSize(S32 oldSize, S32 newSize);
bool removeDetail(S32 size);
bool addSequence(const Torque::Path& path, const String& fromSeq, const String& name, S32 startFrame, S32 endFrame, bool padRotKeys, bool padTransKeys);
bool removeSequence(const String& name);
bool addTrigger(const String& seqName, S32 keyframe, S32 state);
bool removeTrigger(const String& seqName, S32 keyframe, S32 state);
bool setSequenceBlend(const String& seqName, bool blend, const String& blendRefSeqName, S32 blendRefFrame);
bool setSequenceGroundSpeed(const String& seqName, const Point3F& trans, const Point3F& rot);
/// @}
};
#define TSNode TSShape::Node
#define TSObject TSShape::Object
#define TSSequence TSShape::Sequence
#define TSDetail TSShape::Detail
inline QuatF & TSShape::getRotation(const Sequence & seq, S32 keyframeNum, S32 rotNum, QuatF * quat) const
{
return nodeRotations[seq.baseRotation + rotNum*seq.numKeyframes + keyframeNum].getQuatF(quat);
}
inline const Point3F & TSShape::getTranslation(const Sequence & seq, S32 keyframeNum, S32 tranNum) const
{
return nodeTranslations[seq.baseTranslation + tranNum*seq.numKeyframes + keyframeNum];
}
inline F32 TSShape::getUniformScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const
{
return nodeUniformScales[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum];
}
inline const Point3F & TSShape::getAlignedScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum) const
{
return nodeAlignedScales[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum];
}
inline TSScale & TSShape::getArbitraryScale(const Sequence & seq, S32 keyframeNum, S32 scaleNum, TSScale * scale) const
{
nodeArbitraryScaleRots[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum].getQuatF(&scale->mRotate);
scale->mScale = nodeArbitraryScaleFactors[seq.baseScale + scaleNum*seq.numKeyframes + keyframeNum];
return *scale;
}
inline const TSShape::ObjectState & TSShape::getObjectState(const Sequence & seq, S32 keyframeNum, S32 objectNum) const
{
return objectStates[seq.baseObjectState + objectNum*seq.numKeyframes + keyframeNum];
}
#endif

View file

@ -0,0 +1,229 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsShapeAlloc.h"
#define readOnly() AssertFatal(mMode==TSShapeAlloc::ReadMode, "TSShapeAlloc: write-only function called when reading")
#define writeOnly() AssertFatal(mMode==TSShapeAlloc::WriteMode,"TSShapeAlloc: read-only function called when writing")
void TSShapeAlloc::setRead(S32 * memBuffer32, S16 * memBuffer16, S8 * memBuffer8, bool clear)
{
mMemBuffer32 = memBuffer32;
mMemBuffer16 = memBuffer16;
mMemBuffer8 = memBuffer8 ;
mMemGuard32 = 0;
mMemGuard16 = 0;
mMemGuard8 = 0;
mSaveGuard32 = 0;
mSaveGuard16 = 0;
mSaveGuard8 = 0;
if (clear)
{
mDest = NULL;
mSize = 0;
}
setSkipMode(false);
mMode = TSShapeAlloc::ReadMode;
}
void TSShapeAlloc::setWrite()
{
mMemBuffer32 = 0;
mMemBuffer16 = 0;
mMemBuffer8 = 0;
mSize32 = mFullSize32 = 0;
mSize16 = mFullSize16 = 0;
mSize8 = mFullSize8 = 0;
mMemGuard32 = 0;
mMemGuard16 = 0;
mMemGuard8 = 0;
setSkipMode(false); // doesn't really do anything here...
mMode = TSShapeAlloc::WriteMode;
}
void TSShapeAlloc::doAlloc()
{
readOnly();
mDest = new S8[mSize];
mSize = 0;
}
void TSShapeAlloc::align32()
{
readOnly();
S32 aligned = mSize+3 & (~0x3);
allocShape8(aligned-mSize);
}
#define IMPLEMENT_ALLOC(suffix,type) \
\
type TSShapeAlloc::get##suffix() \
{ \
readOnly(); \
return *(mMemBuffer##suffix++); \
} \
\
void TSShapeAlloc::get##suffix(type * dest, S32 num) \
{ \
readOnly(); \
dMemcpy(dest,mMemBuffer##suffix,sizeof(type)*num); \
mMemBuffer##suffix += num; \
} \
\
type * TSShapeAlloc::allocShape##suffix(S32 num) \
{ \
readOnly(); \
type * ret = (type*) mDest; \
if (mDest) \
mDest += mMult*num*sizeof(type); \
mSize += sizeof(type)*mMult*num; \
return ret; \
} \
\
type * TSShapeAlloc::getPointer##suffix(S32 num) \
{ \
readOnly(); \
type * ret = (type*)mMemBuffer##suffix; \
mMemBuffer##suffix += num; \
return ret; \
} \
\
type * TSShapeAlloc::copyToShape##suffix(S32 num, bool returnSomething) \
{ \
readOnly(); \
type * ret = (!returnSomething || mDest) ? (type*)mDest : mMemBuffer##suffix; \
if (mDest) \
{ \
dMemcpy((S8*)mDest,(S8*)mMemBuffer##suffix, \
mMult*num*sizeof(type)); \
mDest += mMult*num*sizeof(type); \
} \
mMemBuffer##suffix += num; \
mSize += sizeof(type)*mMult*num; \
return ret; \
} \
\
bool TSShapeAlloc::checkGuard##suffix() \
{ \
readOnly(); \
mSaveGuard##suffix=get##suffix(); \
bool ret = (mSaveGuard##suffix==mMemGuard##suffix); \
mMemGuard##suffix += 1; \
return ret; \
} \
\
type TSShapeAlloc::getPrevGuard##suffix() \
{ \
readOnly(); \
return mMemGuard##suffix - 1; \
} \
\
type TSShapeAlloc::getSaveGuard##suffix() \
{ \
readOnly(); \
return mSaveGuard##suffix; \
} \
\
type * TSShapeAlloc::getBuffer##suffix() \
{ \
writeOnly(); \
return mMemBuffer##suffix; \
} \
\
S32 TSShapeAlloc::getBufferSize##suffix() \
{ \
writeOnly(); \
return mSize##suffix; \
} \
\
type * TSShapeAlloc::extend##suffix(S32 add) \
{ \
writeOnly(); \
if (mSize##suffix+add>mFullSize##suffix) \
{ \
S32 numPages = 1+(mFullSize##suffix+add)/TSShapeAlloc::PageSize; \
mFullSize##suffix = numPages*TSShapeAlloc::PageSize; \
type * temp = new type[mFullSize##suffix]; \
dMemcpy(temp,mMemBuffer##suffix, mSize##suffix * sizeof(type)); \
delete [] mMemBuffer##suffix; \
mMemBuffer##suffix = temp; \
} \
type * ret = mMemBuffer##suffix + mSize##suffix; \
mSize##suffix += add; \
return ret; \
} \
\
type TSShapeAlloc::set##suffix(type entry) \
{ \
writeOnly(); \
*extend##suffix(1) = entry; \
return entry; \
} \
\
void TSShapeAlloc::copyToBuffer##suffix(type * entries, S32 count) \
{ \
writeOnly(); \
if (entries) \
dMemcpy((U8*)extend##suffix(count),(U8*)entries,count*sizeof(type)); \
else \
dMemset((U8*)extend##suffix(count),0,count*sizeof(type)); \
} \
\
void TSShapeAlloc::setGuard##suffix() \
{ \
writeOnly(); \
set##suffix(mMemGuard##suffix); \
mMemGuard##suffix += 1; \
}
IMPLEMENT_ALLOC(32,S32)
IMPLEMENT_ALLOC(16,S16)
IMPLEMENT_ALLOC(8,S8)
void TSShapeAlloc::checkGuard()
{
bool check32 = checkGuard32();
bool check16 = checkGuard16();
bool check8 = checkGuard8();
AssertFatal(check32,avar("TSShapeAlloc::checkGuard32: found %i, wanted %i",getSaveGuard32(),getPrevGuard32()));
AssertFatal(check16,avar("TSShapeAlloc::checkGuard16: found %i, wanted %i",getSaveGuard16(),getPrevGuard16()));
AssertFatal(check8 ,avar("TSShapeAlloc::checkGuard8: found %i, wanted %i",getSaveGuard8() ,getPrevGuard8()));
}
void TSShapeAlloc::setGuard()
{
setGuard32();
setGuard16();
setGuard8();
}

View file

@ -0,0 +1,156 @@
//-----------------------------------------------------------------------------
// 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 _TSSHAPEALLOC_H_
#define _TSSHAPEALLOC_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
/// Alloc structure used in the reading/writing of shapes.
///
/// In read mode we assemble contents of 32-bit, 16-bit, and 8-bit buffers
/// into a single destination buffer.
///
/// In write mode we dissemble a stream of memory (which may be scattered in physical memory)
/// into 32-bit, 16-bit, 8-bit, Point3F, and Point2F buffers using function calls similar
/// to the read calls.
///
/// Read usage:
/// 1. call "setRead" with each incoming memory buffers and clear=true.
/// 2. run through set of operations for allocating and transfering memory to target buffer
/// these are the operations under "DECLARE_ALLOC" that call readOnly in the .cc file.
/// 3. call "doAlloc" to create buffer exactly as large as needed.
/// 4. repeat step 1 & 2 to do the actual transfer of memory, except with clear=false
/// (note: first time through nothing was copied to the shape, we only kept track
/// of the size of the transfer).
/// 5. call getBuffer to get the target (destination buffer)
///
/// write usage:
/// 1. call "setWrite" (no parameters).
/// 2. run through set of operations for allocating and transfering memory to internal buffers
/// these are the operations under "DECLARE_ALLOC" that call writeOnly in the .cc file.
/// 3. call getBuffer32 and getBufferSize32 to get 32-bit buffer and size. Similarly for
/// 16-bit, 8-bit (getBuffer16, getBuffer8).
///
/// TSShape::assesmbleShape and TSShape::dissembleShape can be used as examples
class TSShapeAlloc
{
S32 mMode; ///< read or write
/// reading and writing (when reading these are the input; when writing these are the output)
S32 * mMemBuffer32;
S16 * mMemBuffer16;
S8 * mMemBuffer8;
/// for writing only...
S32 mSize32;
S32 mSize16;
S32 mSize8;
S32 mFullSize32;
S32 mFullSize16;
S32 mFullSize8;
/// reading and writing...
S32 mMemGuard32;
S16 mMemGuard16;
S8 mMemGuard8;
/// reading
S32 mSaveGuard32;
S16 mSaveGuard16;
S8 mSaveGuard8;
/// reading only...this is the output
S8 * mDest;
S32 mSize;
S32 mMult; ///< mult incoming sizes by this (when 0, then mDest doesn't grow --> skip mode)
public:
enum { ReadMode = 0, WriteMode = 1, PageSize = 1024 }; ///< PageSize must be multiple of 4 so that we can always
///< "over-read" up to next dword
void setRead(S32 * buff32, S16 * buff16, S8 * buff8, bool clear);
void setWrite();
// reading only...
void doAlloc();
void align32(); ///< align on dword boundary
S8 * getBuffer() { return mDest; }
S32 getSize() { return mSize; }
void setSkipMode(bool skip) { mMult = skip ? 0 : 1; }
/// @name Reading Operations:
///
/// get(): reads one or more entries of type from input buffer (doesn't affect output buffer)
///
/// copyToShape(): copies entries of type from input buffer to output buffer
///
/// allocShape(): creates room for entries of type in output buffer (no effect on input buffer)
///
/// getPointer(): gets pointer to next entries of type in input buffer (no effect on input buffer)
///
/// @note all operations advance current "position" of input and output buffers
/// writing operations:
///
/// set(): adds one entry to appropriate buffer
///
/// copyToBuffer(): adds count entries to approrpiate buffer
///
/// getBuffer(): returns associated buffer (i.e., getBuffer32 gets 32bit buffer)
///
/// getBufferSize(): returns size of associated buffer
///
/// @{
#define DECLARE_ALLOC(suffix,type) \
type get##suffix(); \
void get##suffix(type*,S32); \
type * copyToShape##suffix(S32,bool returnSomething=false); \
type * getPointer##suffix(S32); \
type * allocShape##suffix(S32); \
bool checkGuard##suffix(); \
type getPrevGuard##suffix(); \
type getSaveGuard##suffix(); \
type * getBuffer##suffix(); \
S32 getBufferSize##suffix(); \
void setGuard##suffix(); \
type * extend##suffix(S32); \
type set##suffix(type); \
void copyToBuffer##suffix(type*,S32);
DECLARE_ALLOC(32,S32)
DECLARE_ALLOC(16,S16)
DECLARE_ALLOC(8,S8)
/// @}
void checkGuard();
void setGuard();
};
#endif // _H_TS_SHAPE_ALLOC_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,406 @@
//-----------------------------------------------------------------------------
// 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 _TSSHAPECONSTRUCT_H_
#define _TSSHAPECONSTRUCT_H_
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _MTRANSFORM_H_
#include "math/mTransform.h"
#endif
#ifndef _TSSHAPE_H_
#include "ts/tsShape.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _COLLADA_UTILS_H_
#include "ts/collada/colladaUtils.h"
#endif
/// This class allows an artist to export their animations for the model
/// into the .dsq format. This class in particular matches up the model
/// with the .dsqs to create a nice animated model.
class TSShapeConstructor : public SimObject
{
typedef SimObject Parent;
public:
struct ChangeSet
{
enum eCommandType
{
CmdAddNode,
CmdRemoveNode,
CmdRenameNode,
CmdSetNodeTransform,
CmdSetNodeParent,
CmdAddMesh,
CmdAddPrimitive,
CmdRemoveMesh,
CmdSetMeshSize,
CmdSetMeshType,
CmdSetMeshMaterial,
CmdRemoveObject,
CmdRenameObject,
CmdSetObjectNode,
CmdSetBounds,
CmdRenameDetailLevel,
CmdRemoveDetailLevel,
CmdSetDetailLevelSize,
CmdAddImposter,
CmdRemoveImposter,
CmdAddCollisionDetail,
CmdAddSequence,
CmdRemoveSequence,
CmdRenameSequence,
CmdSetSequenceCyclic,
CmdSetSequenceBlend,
CmdSetSequencePriority,
CmdSetSequenceGroundSpeed,
CmdAddTrigger,
CmdRemoveTrigger,
CmdInvalid
};
struct Command
{
eCommandType type; // Command type
StringTableEntry name; // Command name
String argv[10]; // Command arguments
S32 argc; // Number of arguments
Command() : type(CmdInvalid), name(0), argc(0) { }
Command( const char* _name )
: type(CmdInvalid), argc(0)
{
name = StringTable->insert( _name );
}
// Helper functions to fill in the command arguments
inline void addArgs() { }
template< typename A >
inline void addArgs( A a )
{
argv[argc++] = EngineMarshallData( a );
}
template< typename A, typename B > void addArgs( A a, B b )
{
addArgs( a );
addArgs( b );
}
template< typename A, typename B, typename C >
inline void addArgs( A a, B b, C c )
{
addArgs( a );
addArgs( b, c );
}
template< typename A, typename B, typename C, typename D >
inline void addArgs( A a, B b, C c, D d )
{
addArgs( a );
addArgs( b, c, d );
}
template< typename A, typename B, typename C, typename D, typename E >
inline void addArgs( A a, B b, C c, D d, E e )
{
addArgs( a );
addArgs( b, c, d, e );
}
template< typename A, typename B, typename C, typename D, typename E, typename F >
inline void addArgs( A a, B b, C c, D d, E e, F f )
{
addArgs( a );
addArgs( b, c, d, e, f );
}
template< typename A, typename B, typename C, typename D, typename E, typename F, typename G >
inline void addArgs( A a, B b, C c, D d, E e, F f, G g )
{
addArgs( a );
addArgs( b, c, d, e, f, g );
}
template< typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H >
inline void addArgs( A a, B b, C c, D d, E e, F f, G g, H h )
{
addArgs( a );
addArgs( b, c, d, e, f, g, h );
}
template< typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I >
inline void addArgs( A a, B b, C c, D d, E e, F f, G g, H h, I i )
{
addArgs( a );
addArgs( b, c, d, e, f, g, h, i );
}
template< typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J >
inline void addArgs( A a, B b, C c, D d, E e, F f, G g, H h, I i, J j )
{
addArgs( a );
addArgs( b, c, d, e, f, g, h, i, j );
}
};
Vector<Command> mCommands;
eCommandType getCmdType(const char* name);
void clear() { mCommands.clear(); }
bool empty() { return mCommands.empty(); }
void add( Command& cmd );
// These methods handle change set optimisation based on the newly added command
bool addCmd_setNodeParent( const Command& newCmd );
bool addCmd_setNodeTransform( const Command& newCmd );
bool addCmd_renameNode( const Command& newCmd );
bool addCmd_removeNode( const Command& newCmd );
bool addCmd_setMeshSize( const Command& newCmd );
bool addCmd_setMeshType( const Command& newCmd );
bool addCmd_setMeshMaterial( const Command& newCmd );
bool addCmd_removeMesh( const Command& newCmd );
bool addCmd_setObjectNode( const Command& newCmd );
bool addCmd_renameObject( const Command& newCmd );
bool addCmd_removeObject( const Command& newCmd );
bool addCmd_setBounds( const Command& newCmd );
bool addCmd_renameDetailLevel( const Command& newCmd );
bool addCmd_removeDetailLevel( const Command& newCmd );
bool addCmd_setDetailSize( const Command& newCmd );
bool addCmd_addImposter( const Command& newCmd );
bool addCmd_removeImposter( const Command& newCmd );
bool addCmd_addSequence( Command& newCmd );
bool addCmd_setSequencePriority( const Command& newCmd );
bool addCmd_setSequenceGroundSpeed( const Command& newCmd );
bool addCmd_setSequenceCyclic( const Command& newCmd );
bool addCmd_setSequenceBlend( const Command& newCmd );
bool addCmd_renameSequence( const Command& newCmd );
bool addCmd_removeSequence( const Command& newCmd );
bool addCmd_addTrigger( const Command& newCmd );
bool addCmd_removeTrigger( const Command& newCmd );
void write(TSShape* shape, Stream& stream, const String& savePath);
};
static const int MaxLegacySequences = 127;
protected:
FileName mShapePath;
Vector<FileName> mSequences;
ChangeSet mChangeSet;
static bool addSequenceFromField( void *obj, const char *index, const char *data );
static void _onTSShapeLoaded( Resource< TSShape >& shape );
static void _onTSShapeUnloaded( const Torque::Path& path, TSShape* shape );
static ResourceRegisterPostLoadSignal< TSShape > _smAutoLoad;
static ResourceRegisterUnloadSignal< TSShape > _smAutoUnload;
/// @name Callbacks
///@{
DECLARE_CALLBACK( void, onLoad, () );
DECLARE_CALLBACK( void, onUnload, () );
///@}
virtual void _onLoad( TSShape* shape );
virtual void _onUnload();
public:
TSShape* mShape; // Edited shape; NULL while not loaded; not a Resource<TSShape> as we don't want it to prevent from unloading.
ColladaUtils::ImportOptions mOptions;
public:
TSShapeConstructor();
TSShapeConstructor(const String& path) : mShapePath(path) { }
~TSShapeConstructor();
DECLARE_CONOBJECT(TSShapeConstructor);
static void initPersistFields();
static TSShapeConstructor* findShapeConstructor(const FileName& path);
bool onAdd();
void onScriptChanged(const Torque::Path& path);
bool writeField(StringTableEntry fieldname, const char *value);
void writeChangeSet();
void notifyShapeChanged();
TSShape* getShape() const { return mShape; }
const String& getShapePath() const { return mShapePath; }
/// @name Dumping
///@{
void dumpShape( const char* filename );
void saveShape( const char* filename );
///@}
/// @name Nodes
///@{
S32 getNodeCount();
S32 getNodeIndex( const char* name );
const char* getNodeName( S32 index );
const char* getNodeParentName( const char* name );
bool setNodeParent( const char* name, const char* parentName );
S32 getNodeChildCount( const char* name );
const char* getNodeChildName( const char* name, S32 index );
S32 getNodeObjectCount( const char* name );
const char* getNodeObjectName( const char* name, S32 index );
TransformF getNodeTransform( const char* name, bool isWorld=false );
bool setNodeTransform( const char* name, TransformF txfm, bool isWorld=false );
bool renameNode( const char* oldName, const char* newName );
bool addNode( const char* name, const char* parentName, TransformF txfm=TransformF::Identity, bool isWorld=false);
bool removeNode( const char* name );
///@}
/// @name Materials
///@{
S32 getTargetCount();
const char* getTargetName( S32 index );
///@}
///@{
S32 getObjectCount();
const char* getObjectName( S32 index );
S32 getObjectIndex( const char* name );
const char* getObjectNode( const char* name );
bool setObjectNode( const char* objName, const char* nodeName );
bool renameObject( const char* oldName, const char* newName );
bool removeObject( const char* name );
///@}
/// @name Meshes
///@{
S32 getMeshCount( const char* name );
const char* getMeshName( const char* name, S32 index );
S32 getMeshSize( const char* name, S32 index );
bool setMeshSize( const char* name, S32 size );
const char* getMeshType( const char* name );
bool setMeshType( const char* name, const char* type );
const char* getMeshMaterial( const char* name );
bool setMeshMaterial( const char* meshName, const char* matName );
bool addMesh( const char* meshName, const char* srcShape, const char* srcMesh );
bool addPrimitive( const char* meshName, const char* type, const char* params, TransformF txfm, const char* nodeName );
bool removeMesh( const char* name );
///@}
/// @name Detail Levels
///@{
Box3F getBounds();
bool setBounds( Box3F bbox );
S32 getDetailLevelCount();
const char* getDetailLevelName( S32 index );
S32 getDetailLevelSize( S32 index);
S32 getDetailLevelIndex( S32 size );
bool renameDetailLevel( const char* oldName, const char* newName );
bool removeDetailLevel( S32 index );
S32 setDetailLevelSize( S32 index, S32 newSize );
S32 getImposterDetailLevel();
const char* getImposterSettings( S32 index );
S32 addImposter( S32 size, S32 equatorSteps, S32 polarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle );
bool removeImposter();
bool addCollisionDetail( S32 size, const char* type, const char* target, S32 depth=4, F32 merge=30.0f, F32 concavity=30.0f, S32 maxVerts=32, F32 boxMaxError=0, F32 sphereMaxError=0, F32 capsuleMaxError=0 );
///@}
/// @name Sequences
///@{
S32 getSequenceCount();
S32 getSequenceIndex( const char* name);
const char* getSequenceName( S32 index );
const char* getSequenceSource( const char* name );
S32 getSequenceFrameCount( const char* name );
F32 getSequencePriority( const char* name );
bool setSequencePriority( const char* name, F32 priority );
const char* getSequenceGroundSpeed( const char* name );
bool setSequenceGroundSpeed( const char* name, Point3F transSpeed, Point3F rotSpeed=Point3F::Zero );
bool getSequenceCyclic( const char* name );
bool setSequenceCyclic( const char* name, bool cyclic );
const char* getSequenceBlend( const char* name );
bool setSequenceBlend( const char* name, bool blend, const char* blendSeq, S32 blendFrame );
bool renameSequence( const char* oldName, const char* newName );
bool addSequence( const char* source, const char* name, S32 start=0, S32 end=-1, bool padRot=true, bool padTrans=false );
bool removeSequence( const char* name );
///@}
/// @name Triggers
///@{
S32 getTriggerCount( const char* name );
const char* getTrigger( const char* name, S32 index );
bool addTrigger( const char* name, S32 keyframe, S32 state );
bool removeTrigger( const char* name, S32 keyframe, S32 state );
///@}
};
typedef domUpAxisType TSShapeConstructorUpAxis;
typedef ColladaUtils::ImportOptions::eLodType TSShapeConstructorLodType;
DefineEnumType( TSShapeConstructorUpAxis );
DefineEnumType( TSShapeConstructorLodType );
/* This macro simplifies the definition of a TSShapeConstructor API method. It
wraps the actual EngineMethod definition and automatically calls the real
class method. It also creates a ChangeSet::Comand (with all arguments stored
as strings). The one drawback is that it includes the open brace for the real
class method, so to keep the code looking mostly normal, such methods start
with another open brace, and end with a double closing brace. Not perfect,
but a lot better than having to type out the argument list multiple times for
the 50 odd API functions. */
#define DefineTSShapeConstructorMethod( name, retType, args, defArgs, rawArgs, defRet, usage ) \
DefineEngineMethod( TSShapeConstructor, name, retType, args, defArgs, usage ) \
{ \
/* Check that shape is loaded */ \
if( !object->getShape() ) \
{ \
Con::errorf( "TSShapeConstructor::" #name " - shape not loaded" ); \
return defRet; \
} \
return object->name rawArgs ; \
} \
/* Define the real TSShapeConstructor method */ \
retType TSShapeConstructor::name args \
{ \
/* Initialise change set command (may or may not be added) */ \
TSShapeConstructor::ChangeSet::Command newCmd( #name ); \
newCmd.addArgs rawArgs ; \
TORQUE_UNUSED(newCmd);
/* This macro just hides the name of the auto-created ChangeSet::Command from
above, so we are free to change the implementation later if needed */
#define ADD_TO_CHANGE_SET() mChangeSet.add( newCmd );
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,778 @@
//-----------------------------------------------------------------------------
// 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/tsShapeInstance.h"
#include "ts/tsLastDetail.h"
#include "ts/tsMaterialList.h"
#include "console/consoleTypes.h"
#include "ts/tsDecal.h"
#include "platform/profiler.h"
#include "core/frameAllocator.h"
#include "gfx/gfxDevice.h"
#include "materials/materialManager.h"
#include "materials/materialFeatureTypes.h"
#include "materials/sceneData.h"
#include "materials/matInstance.h"
#include "scene/sceneRenderState.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "core/module.h"
MODULE_BEGIN( TSShapeInstance )
MODULE_INIT
{
Con::addVariable("$pref::TS::detailAdjust", TypeF32, &TSShapeInstance::smDetailAdjust,
"@brief User perference for scaling the TSShape level of detail.\n"
"The smaller the value the closer the camera must get to see the "
"highest LOD. This setting can have a huge impact on performance in "
"mesh heavy scenes. The default value is 1.\n"
"@ingroup Rendering\n" );
Con::addVariable("$pref::TS::skipLoadDLs", TypeS32, &TSShape::smNumSkipLoadDetails,
"@brief User perference which causes TSShapes to skip loading higher lods.\n"
"This potentialy reduces the GPU resources and materials generated as well as "
"limits the LODs rendered. The default value is 0.\n"
"@see $pref::TS::skipRenderDLs\n"
"@ingroup Rendering\n" );
Con::addVariable("$pref::TS::skipRenderDLs", TypeS32, &TSShapeInstance::smNumSkipRenderDetails,
"@brief User perference which causes TSShapes to skip rendering higher lods.\n"
"This will reduce the number of draw calls and triangles rendered and improve "
"rendering performance when proper LODs have been created for your models. "
"The default value is 0.\n"
"@see $pref::TS::skipLoadDLs\n"
"@ingroup Rendering\n" );
Con::addVariable("$pref::TS::smallestVisiblePixelSize", TypeF32, &TSShapeInstance::smSmallestVisiblePixelSize,
"@brief User perference which sets the smallest pixel size at which TSShapes will skip rendering.\n"
"This will force all shapes to stop rendering when they get smaller than this size. "
"The default value is -1 which disables it.\n"
"@ingroup Rendering\n" );
Con::addVariable("$pref::TS::maxInstancingVerts", TypeS32, &TSMesh::smMaxInstancingVerts,
"@brief Enables mesh instancing on non-skin meshes that have less that this count of verts.\n"
"The default value is 200. Higher values can degrade performance.\n"
"@ingroup Rendering\n" );
}
MODULE_END;
F32 TSShapeInstance::smDetailAdjust = 1.0f;
F32 TSShapeInstance::smSmallestVisiblePixelSize = -1.0f;
S32 TSShapeInstance::smNumSkipRenderDetails = 0;
F32 TSShapeInstance::smLastScreenErrorTolerance = 0.0f;
F32 TSShapeInstance::smLastScaledDistance = 0.0f;
F32 TSShapeInstance::smLastPixelSize = 0.0f;
Vector<QuatF> TSShapeInstance::smNodeCurrentRotations(__FILE__, __LINE__);
Vector<Point3F> TSShapeInstance::smNodeCurrentTranslations(__FILE__, __LINE__);
Vector<F32> TSShapeInstance::smNodeCurrentUniformScales(__FILE__, __LINE__);
Vector<Point3F> TSShapeInstance::smNodeCurrentAlignedScales(__FILE__, __LINE__);
Vector<TSScale> TSShapeInstance::smNodeCurrentArbitraryScales(__FILE__, __LINE__);
Vector<MatrixF> TSShapeInstance::smNodeLocalTransforms(__FILE__, __LINE__);
TSIntegerSet TSShapeInstance::smNodeLocalTransformDirty;
Vector<TSThread*> TSShapeInstance::smRotationThreads(__FILE__, __LINE__);
Vector<TSThread*> TSShapeInstance::smTranslationThreads(__FILE__, __LINE__);
Vector<TSThread*> TSShapeInstance::smScaleThreads(__FILE__, __LINE__);
//-------------------------------------------------------------------------------------
// constructors, destructors, initialization
//-------------------------------------------------------------------------------------
TSShapeInstance::TSShapeInstance( const Resource<TSShape> &shape, bool loadMaterials )
{
VECTOR_SET_ASSOCIATION(mMeshObjects);
VECTOR_SET_ASSOCIATION(mNodeTransforms);
VECTOR_SET_ASSOCIATION(mNodeReferenceRotations);
VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations);
VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales);
VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors);
VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots);
VECTOR_SET_ASSOCIATION(mThreadList);
VECTOR_SET_ASSOCIATION(mTransitionThreads);
mShapeResource = shape;
mShape = mShapeResource;
buildInstanceData( mShape, loadMaterials );
}
TSShapeInstance::TSShapeInstance( TSShape *shape, bool loadMaterials )
{
VECTOR_SET_ASSOCIATION(mMeshObjects);
VECTOR_SET_ASSOCIATION(mNodeTransforms);
VECTOR_SET_ASSOCIATION(mNodeReferenceRotations);
VECTOR_SET_ASSOCIATION(mNodeReferenceTranslations);
VECTOR_SET_ASSOCIATION(mNodeReferenceUniformScales);
VECTOR_SET_ASSOCIATION(mNodeReferenceScaleFactors);
VECTOR_SET_ASSOCIATION(mNodeReferenceArbitraryScaleRots);
VECTOR_SET_ASSOCIATION(mThreadList);
VECTOR_SET_ASSOCIATION(mTransitionThreads);
mShapeResource = NULL;
mShape = shape;
buildInstanceData( mShape, loadMaterials );
}
TSShapeInstance::~TSShapeInstance()
{
mMeshObjects.clear();
while (mThreadList.size())
destroyThread(mThreadList.last());
setMaterialList(NULL);
delete [] mDirtyFlags;
}
void TSShapeInstance::buildInstanceData(TSShape * _shape, bool loadMaterials)
{
mShape = _shape;
debrisRefCount = 0;
mCurrentDetailLevel = 0;
mCurrentIntraDetailLevel = 1.0f;
// all triggers off at start
mTriggerStates = 0;
//
mAlphaAlways = false;
mAlphaAlwaysValue = 1.0f;
// material list...
mMaterialList = NULL;
mOwnMaterialList = false;
//
mData = 0;
mScaleCurrentlyAnimated = false;
if(loadMaterials)
setMaterialList(mShape->materialList);
// set up node data
initNodeTransforms();
// add objects to trees
initMeshObjects();
// set up subtree data
S32 ss = mShape->subShapeFirstNode.size(); // we have this many subtrees
mDirtyFlags = new U32[ss];
mGroundThread = NULL;
mCurrentDetailLevel = 0;
animateSubtrees();
// Construct billboards if not done already
if ( loadMaterials && mShapeResource )
mShape->setupBillboardDetails( mShapeResource.getPath().getFullPath() );
}
void TSShapeInstance::initNodeTransforms()
{
// set up node data
S32 numNodes = mShape->nodes.size();
mNodeTransforms.setSize(numNodes);
}
void TSShapeInstance::initMeshObjects()
{
// add objects to trees
S32 numObjects = mShape->objects.size();
mMeshObjects.setSize(numObjects);
for (S32 i=0; i<numObjects; i++)
{
const TSObject * obj = &mShape->objects[i];
MeshObjectInstance * objInst = &mMeshObjects[i];
// hook up the object to it's node and transforms.
objInst->mTransforms = &mNodeTransforms;
objInst->nodeIndex = obj->nodeIndex;
// set up list of meshes
if (obj->numMeshes)
objInst->meshList = &mShape->meshes[obj->startMeshIndex];
else
objInst->meshList = NULL;
objInst->object = obj;
objInst->forceHidden = false;
}
}
void TSShapeInstance::setMaterialList( TSMaterialList *matList )
{
// get rid of old list
if ( mOwnMaterialList )
delete mMaterialList;
mMaterialList = matList;
mOwnMaterialList = false;
// If the material list is already be mapped then
// don't bother doing the initializing a second time.
// Note: only check the last material instance as this will catch both
// uninitialised lists, as well as initialised lists that have had new
// materials appended
if ( mMaterialList && !mMaterialList->getMaterialInst( mMaterialList->size()-1 ) )
{
mMaterialList->setTextureLookupPath( mShapeResource.getPath().getPath() );
mMaterialList->mapMaterials();
Material::sAllowTextureTargetAssignment = true;
initMaterialList();
Material::sAllowTextureTargetAssignment = false;
}
}
void TSShapeInstance::cloneMaterialList( const FeatureSet *features )
{
if ( mOwnMaterialList )
return;
mMaterialList = new TSMaterialList(mMaterialList);
initMaterialList( features );
mOwnMaterialList = true;
}
void TSShapeInstance::initMaterialList( const FeatureSet *features )
{
// If we don't have features then use the default.
if ( !features )
features = &MATMGR->getDefaultFeatures();
// Initialize the materials.
mMaterialList->initMatInstances( *features, mShape->getVertexFormat() );
// TODO: It would be good to go thru all the meshes and
// pre-create all the active material hooks for shadows,
// reflections, and instancing. This would keep these
// hiccups from happening at runtime.
}
void TSShapeInstance::reSkin( String newBaseName, String oldBaseName )
{
if( newBaseName.isEmpty() )
newBaseName = "base";
if( oldBaseName.isEmpty() )
oldBaseName = "base";
if ( newBaseName.equal( oldBaseName, String::NoCase ) )
return;
const U32 oldBaseNameLength = oldBaseName.length();
// Make our own copy of the materials list from the resource if necessary
if (ownMaterialList() == false)
cloneMaterialList();
TSMaterialList* pMatList = getMaterialList();
pMatList->setTextureLookupPath( mShapeResource.getPath().getPath() );
// Cycle through the materials
const Vector<String> &materialNames = pMatList->getMaterialNameList();
for ( S32 i = 0; i < materialNames.size(); i++ )
{
// Try changing base
const String &pName = materialNames[i];
if ( pName.compare( oldBaseName, oldBaseNameLength, String::NoCase ) == 0 )
{
String newName( pName );
newName.replace( 0, oldBaseNameLength, newBaseName );
pMatList->renameMaterial( i, newName );
}
}
// Initialize the material instances
initMaterialList();
}
//-------------------------------------------------------------------------------------
// Render & detail selection
//-------------------------------------------------------------------------------------
void TSShapeInstance::renderDebugNormals( F32 normalScalar, S32 dl )
{
if ( dl < 0 )
return;
AssertFatal( dl >= 0 && dl < mShape->details.size(),
"TSShapeInstance::renderDebugNormals() - Bad detail level!" );
static GFXStateBlockRef sb;
if ( sb.isNull() )
{
GFXStateBlockDesc desc;
desc.setCullMode( GFXCullNone );
desc.setZReadWrite( true );
desc.zWriteEnable = false;
desc.vertexColorEnable = true;
sb = GFX->createStateBlock( desc );
}
GFX->setStateBlock( sb );
const TSDetail *detail = &mShape->details[dl];
const S32 ss = detail->subShapeNum;
if ( ss < 0 )
return;
const S32 start = mShape->subShapeFirstObject[ss];
const S32 end = start + mShape->subShapeNumObjects[ss];
for ( S32 i = start; i < end; i++ )
{
MeshObjectInstance *meshObj = &mMeshObjects[i];
if ( !meshObj )
continue;
const MatrixF &meshMat = meshObj->getTransform();
// Then go through each TSMesh...
U32 m = 0;
for( TSMesh *mesh = meshObj->getMesh(m); mesh != NULL; mesh = meshObj->getMesh(m++) )
{
// and pull out the list of normals.
const U32 numNrms = mesh->mNumVerts;
PrimBuild::begin( GFXLineList, 2 * numNrms );
for ( U32 n = 0; n < numNrms; n++ )
{
Point3F norm = mesh->mVertexData[n].normal();
Point3F vert = mesh->mVertexData[n].vert();
meshMat.mulP( vert );
meshMat.mulV( norm );
// Then render them.
PrimBuild::color4f( mFabs( norm.x ), mFabs( norm.y ), mFabs( norm.z ), 1.0f );
PrimBuild::vertex3fv( vert );
PrimBuild::vertex3fv( vert + (norm * normalScalar) );
}
PrimBuild::end();
}
}
}
void TSShapeInstance::renderDebugNodes()
{
GFXDrawUtil *drawUtil = GFX->getDrawUtil();
ColorI color( 255, 0, 0, 255 );
GFXStateBlockDesc desc;
desc.setBlend( false );
desc.setZReadWrite( false, false );
for ( U32 i = 0; i < mNodeTransforms.size(); i++ )
drawUtil->drawTransform( desc, mNodeTransforms[i], NULL, NULL );
}
void TSShapeInstance::listMeshes( const String &state ) const
{
if ( state.equal( "All", String::NoCase ) )
{
for ( U32 i = 0; i < mMeshObjects.size(); i++ )
{
const MeshObjectInstance &mesh = mMeshObjects[i];
Con::warnf( "meshidx %3d, %8s, %s", i, ( mesh.forceHidden ) ? "Hidden" : "Visible", mShape->getMeshName(i).c_str() );
}
}
else if ( state.equal( "Hidden", String::NoCase ) )
{
for ( U32 i = 0; i < mMeshObjects.size(); i++ )
{
const MeshObjectInstance &mesh = mMeshObjects[i];
if ( mesh.forceHidden )
Con::warnf( "meshidx %3d, %8s, %s", i, "Visible", mShape->getMeshName(i).c_str() );
}
}
else if ( state.equal( "Visible", String::NoCase ) )
{
for ( U32 i = 0; i < mMeshObjects.size(); i++ )
{
const MeshObjectInstance &mesh = mMeshObjects[i];
if ( !mesh.forceHidden )
Con::warnf( "meshidx %3d, %8s, %s", i, "Hidden", mShape->getMeshName(i).c_str() );
}
}
else
{
Con::warnf( "TSShapeInstance::listMeshes( %s ) - only All/Hidden/Visible are valid parameters." );
}
}
void TSShapeInstance::render( const TSRenderState &rdata )
{
if (mCurrentDetailLevel<0)
return;
PROFILE_SCOPE( TSShapeInstance_Render );
// alphaIn: we start to alpha-in next detail level when intraDL > 1-alphaIn-alphaOut
// (finishing when intraDL = 1-alphaOut)
// alphaOut: start to alpha-out this detail level when intraDL > 1-alphaOut
// NOTE:
// intraDL is at 1 when if shape were any closer to us we'd be at dl-1,
// intraDL is at 0 when if shape were any farther away we'd be at dl+1
F32 alphaOut = mShape->alphaOut[mCurrentDetailLevel];
F32 alphaIn = mShape->alphaIn[mCurrentDetailLevel];
F32 saveAA = mAlphaAlways ? mAlphaAlwaysValue : 1.0f;
/// This first case is the single detail level render.
if ( mCurrentIntraDetailLevel > alphaIn + alphaOut )
render( rdata, mCurrentDetailLevel, mCurrentIntraDetailLevel );
else if ( mCurrentIntraDetailLevel > alphaOut )
{
// draw this detail level w/ alpha=1 and next detail level w/
// alpha=1-(intraDl-alphaOut)/alphaIn
// first draw next detail level
if ( mCurrentDetailLevel + 1 < mShape->details.size() && mShape->details[ mCurrentDetailLevel + 1 ].size > 0.0f )
{
setAlphaAlways( saveAA * ( alphaIn + alphaOut - mCurrentIntraDetailLevel ) / alphaIn );
render( rdata, mCurrentDetailLevel + 1, 0.0f );
}
setAlphaAlways( saveAA );
render( rdata, mCurrentDetailLevel, mCurrentIntraDetailLevel );
}
else
{
// draw next detail level w/ alpha=1 and this detail level w/
// alpha = 1-intraDL/alphaOut
// first draw next detail level
if ( mCurrentDetailLevel + 1 < mShape->details.size() && mShape->details[ mCurrentDetailLevel + 1 ].size > 0.0f )
render( rdata, mCurrentDetailLevel+1, 0.0f );
setAlphaAlways( saveAA * mCurrentIntraDetailLevel / alphaOut );
render( rdata, mCurrentDetailLevel, mCurrentIntraDetailLevel );
setAlphaAlways( saveAA );
}
}
void TSShapeInstance::setMeshForceHidden( const char *meshName, bool hidden )
{
Vector<MeshObjectInstance>::iterator iter = mMeshObjects.begin();
for ( ; iter != mMeshObjects.end(); iter++ )
{
S32 nameIndex = iter->object->nameIndex;
const char *name = mShape->names[ nameIndex ];
if ( dStrcmp( meshName, name ) == 0 )
{
iter->forceHidden = hidden;
return;
}
}
}
void TSShapeInstance::setMeshForceHidden( S32 meshIndex, bool hidden )
{
AssertFatal( meshIndex > -1 && meshIndex < mMeshObjects.size(),
"TSShapeInstance::setMeshForceHidden - Invalid index!" );
mMeshObjects[meshIndex].forceHidden = hidden;
}
void TSShapeInstance::render( const TSRenderState &rdata, S32 dl, F32 intraDL )
{
AssertFatal( dl >= 0 && dl < mShape->details.size(),"TSShapeInstance::render" );
S32 i;
const TSDetail * detail = &mShape->details[dl];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
// if we're a billboard detail, draw it and exit
if ( ss < 0 )
{
PROFILE_SCOPE( TSShapeInstance_RenderBillboards );
if ( !rdata.isNoRenderTranslucent() && ( TSLastDetail::smCanShadow || !rdata.getSceneState()->isShadowPass() ) )
mShape->billboardDetails[ dl ]->render( rdata, mAlphaAlways ? mAlphaAlwaysValue : 1.0f );
return;
}
// run through the meshes
S32 start = rdata.isNoRenderNonTranslucent() ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss];
S32 end = rdata.isNoRenderTranslucent() ? mShape->subShapeFirstTranslucentObject[ss] : mShape->subShapeFirstObject[ss] + mShape->subShapeNumObjects[ss];
for (i=start; i<end; i++)
{
// following line is handy for debugging, to see what part of the shape that it is rendering
// const char *name = mShape->names[ mMeshObjects[i].object->nameIndex ];
mMeshObjects[i].render( od, mMaterialList, rdata, mAlphaAlways ? mAlphaAlwaysValue : 1.0f );
}
}
void TSShapeInstance::setCurrentDetail( S32 dl, F32 intraDL )
{
PROFILE_SCOPE( TSShapeInstance_setCurrentDetail );
mCurrentDetailLevel = mClamp( dl, -1, mShape->mSmallestVisibleDL );
mCurrentIntraDetailLevel = intraDL > 1.0f ? 1.0f : (intraDL < 0.0f ? 0.0f : intraDL);
// Restrict the chosen detail level by cutoff value.
if ( smNumSkipRenderDetails > 0 && mCurrentDetailLevel >= 0 )
{
S32 cutoff = getMin( smNumSkipRenderDetails, mShape->mSmallestVisibleDL );
if ( mCurrentDetailLevel < cutoff )
{
mCurrentDetailLevel = cutoff;
mCurrentIntraDetailLevel = 1.0f;
}
}
}
S32 TSShapeInstance::setDetailFromPosAndScale( const SceneRenderState *state,
const Point3F &pos,
const Point3F &scale )
{
VectorF camVector = pos - state->getDiffuseCameraPosition();
F32 dist = getMax( camVector.len(), 0.01f );
F32 invScale = ( 1.0f / getMax( getMax( scale.x, scale.y ), scale.z ) );
return setDetailFromDistance( state, dist * invScale );
}
S32 TSShapeInstance::setDetailFromDistance( const SceneRenderState *state, F32 scaledDistance )
{
PROFILE_SCOPE( TSShapeInstance_setDetailFromDistance );
// For debugging/metrics.
smLastScaledDistance = scaledDistance;
// Shortcut if the distance is really close or negative.
if ( scaledDistance <= 0.0f )
{
mShape->mDetailLevelLookup[0].get( mCurrentDetailLevel, mCurrentIntraDetailLevel );
return mCurrentDetailLevel;
}
// The pixel scale is used the linearly scale the lod
// selection based on the viewport size.
//
// The original calculation from TGEA was...
//
// pixelScale = viewport.extent.x * 1.6f / 640.0f;
//
// Since we now work on the viewport height, assuming
// 4:3 aspect ratio, we've changed the reference value
// to 300 to be more compatible with legacy shapes.
//
const F32 pixelScale = state->getViewport().extent.y / 300.0f;
// This is legacy DTS support for older "multires" based
// meshes. The original crossbow weapon uses this.
//
// If we have more than one detail level and the maxError
// is non-negative then we do some sort of screen error
// metric for detail selection.
//
if ( mShape->mUseDetailFromScreenError )
{
// The pixel size of 1 meter at the input distance.
F32 pixelRadius = state->projectRadius( scaledDistance, 1.0f ) * pixelScale;
static const F32 smScreenError = 5.0f;
return setDetailFromScreenError( smScreenError / pixelRadius );
}
// We're inlining SceneRenderState::projectRadius here to
// skip the unnessasary divide by zero protection.
F32 pixelRadius = ( mShape->radius / scaledDistance ) * state->getWorldToScreenScale().y * pixelScale;
F32 pixelSize = pixelRadius * smDetailAdjust;
if ( pixelSize > smSmallestVisiblePixelSize &&
pixelSize <= mShape->mSmallestVisibleSize )
pixelSize = mShape->mSmallestVisibleSize + 0.01f;
// For debugging/metrics.
smLastPixelSize = pixelSize;
// Clamp it to an acceptable range for the lookup table.
U32 index = (U32)mClampF( pixelSize, 0, mShape->mDetailLevelLookup.size() - 1 );
// Check the lookup table for the detail and intra detail levels.
mShape->mDetailLevelLookup[ index ].get( mCurrentDetailLevel, mCurrentIntraDetailLevel );
// Restrict the chosen detail level by cutoff value.
if ( smNumSkipRenderDetails > 0 && mCurrentDetailLevel >= 0 )
{
S32 cutoff = getMin( smNumSkipRenderDetails, mShape->mSmallestVisibleDL );
if ( mCurrentDetailLevel < cutoff )
{
mCurrentDetailLevel = cutoff;
mCurrentIntraDetailLevel = 1.0f;
}
}
return mCurrentDetailLevel;
}
S32 TSShapeInstance::setDetailFromScreenError( F32 errorTolerance )
{
PROFILE_SCOPE( TSShapeInstance_setDetailFromScreenError );
// For debugging/metrics.
smLastScreenErrorTolerance = errorTolerance;
// note: we use 10 time the average error as the metric...this is
// more robust than the maxError...the factor of 10 is to put average error
// on about the same scale as maxError. The errorTOL is how much
// error we are able to tolerate before going to a more detailed version of the
// shape. We look for a pair of details with errors bounding our errorTOL,
// and then we select an interpolation parameter to tween betwen them. Ok, so
// this isn't exactly an error tolerance. A tween value of 0 is the lower poly
// model (higher detail number) and a value of 1 is the higher poly model (lower
// detail number).
// deal with degenerate case first...
// if smallest detail corresponds to less than half tolerable error, then don't even draw
F32 prevErr;
if ( mShape->mSmallestVisibleDL < 0 )
prevErr = 0.0f;
else
prevErr = 10.0f * mShape->details[mShape->mSmallestVisibleDL].averageError * 20.0f;
if ( mShape->mSmallestVisibleDL < 0 || prevErr < errorTolerance )
{
// draw last detail
mCurrentDetailLevel = mShape->mSmallestVisibleDL;
mCurrentIntraDetailLevel = 0.0f;
return mCurrentDetailLevel;
}
// this function is a little odd
// the reason is that the detail numbers correspond to
// when we stop using a given detail level...
// we search the details from most error to least error
// until we fit under the tolerance (errorTOL) and then
// we use the next highest detail (higher error)
for (S32 i = mShape->mSmallestVisibleDL; i >= 0; i-- )
{
F32 err0 = 10.0f * mShape->details[i].averageError;
if ( err0 < errorTolerance )
{
// ok, stop here
// intraDL = 1 corresponds to fully this detail
// intraDL = 0 corresponds to the next lower (higher number) detail
mCurrentDetailLevel = i;
mCurrentIntraDetailLevel = 1.0f - (errorTolerance - err0) / (prevErr - err0);
return mCurrentDetailLevel;
}
prevErr = err0;
}
// get here if we are drawing at DL==0
mCurrentDetailLevel = 0;
mCurrentIntraDetailLevel = 1.0f;
return mCurrentDetailLevel;
}
//-------------------------------------------------------------------------------------
// Object (MeshObjectInstance & PluginObjectInstance) render methods
//-------------------------------------------------------------------------------------
void TSShapeInstance::ObjectInstance::render( S32, TSMaterialList *, const TSRenderState &rdata, F32 alpha )
{
AssertFatal(0,"TSShapeInstance::ObjectInstance::render: no default render method.");
}
void TSShapeInstance::MeshObjectInstance::render( S32 objectDetail,
TSMaterialList *materials,
const TSRenderState &rdata,
F32 alpha )
{
PROFILE_SCOPE( TSShapeInstance_MeshObjectInstance_render );
if ( forceHidden || ( ( visible * alpha ) <= 0.01f ) )
return;
TSMesh *mesh = getMesh(objectDetail);
if ( !mesh )
return;
const MatrixF &transform = getTransform();
if ( rdata.getCuller() )
{
Box3F box( mesh->getBounds() );
transform.mul( box );
if ( rdata.getCuller()->isCulled( box ) )
return;
}
GFX->pushWorldMatrix();
GFX->multWorld( transform );
mesh->setFade( visible * alpha );
// Pass a hint to the mesh that time has advanced and that the
// skin is dirty and needs to be updated. This should result
// in the skin only updating once per frame in most cases.
const U32 currTime = Sim::getCurrentTime();
bool isSkinDirty = currTime != mLastTime;
mesh->render( materials,
rdata,
isSkinDirty,
*mTransforms,
mVertexBuffer,
mPrimitiveBuffer );
// Update the last render time.
mLastTime = currTime;
GFX->popWorldMatrix();
}
TSShapeInstance::MeshObjectInstance::MeshObjectInstance()
: meshList(0), object(0), frame(0), matFrame(0),
visible(1.0f), forceHidden(false), mLastTime( 0 )
{
}
void TSShapeInstance::prepCollision()
{
PROFILE_SCOPE( TSShapeInstance_PrepCollision );
// Iterate over all our meshes and call prepCollision on them...
for(S32 i=0; i<mShape->meshes.size(); i++)
{
if(mShape->meshes[i])
mShape->meshes[i]->prepOpcodeCollision();
}
}

View file

@ -0,0 +1,795 @@
//-----------------------------------------------------------------------------
// 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 _TSSHAPEINSTANCE_H_
#define _TSSHAPEINSTANCE_H_
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _TSSHAPE_H_
#include "ts/tsShape.h"
#endif
#ifndef _TSINTEGERSET_H_
#include "ts/tsIntegerSet.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
#ifndef _GBITMAP_H_
#include "gfx/bitmap/gBitmap.h"
#endif
#ifndef _TSRENDERDATA_H_
#include "ts/tsRenderState.h"
#endif
#ifndef _TSMATERIALLIST_H_
#include "ts/tsMaterialList.h"
#endif
class RenderItem;
class TSThread;
class ConvexFeature;
class SceneRenderState;
class FeatureSet;
//-------------------------------------------------------------------------------------
// Instance versions of shape objects
//-------------------------------------------------------------------------------------
class TSCallback
{
public:
virtual ~TSCallback() {}
virtual void setNodeTransform(TSShapeInstance * si, S32 nodeIndex, MatrixF & localTransform) = 0;
};
/// An instance of a 3space shape.
///
/// @section TSShapeInstance_intro Introduction
///
/// A 3space model represents a significant amount of data. There are multiple meshes,
/// skeleton information, as well as animation data. Some of this, like the skeletal
/// transforms, are unique for each instance of the model (as different instances are
/// likely to be in different states of animation), while most of it, like texturing
/// information and vertex data, is the same amongst all instances of the shape.
///
/// To keep this data from being replicated for every instance of a 3shape object, Torque
/// uses the ResManager to instantiate and track TSShape objects. TSShape handles reading
/// and writing 3space models, as well as keeping track of static model data, as discussed
/// above. TSShapeInstance keeps track of all instance specific data, such as the currently
/// playing sequences or the active node transforms.
///
/// TSShapeInstance contains all the functionality for 3space models, while TSShape acts as
/// a repository for common data.
///
/// @section TSShapeInstance_functionality What Does TSShapeInstance Do?
///
/// TSShapeInstance handles several areas of functionality:
/// - Collision.
/// - Rendering.
/// - Animation.
/// - Updating skeletal transforms.
/// - Ballooning (see setShapeBalloon() and getShapeBalloon())
///
/// For an excellent example of how to render a TSShape in game, see TSStatic. For examples
/// of how to procedurally animate models, look at Player::updateLookAnimation().
class TSShapeInstance
{
public:
struct ObjectInstance;
friend class TSThread;
friend class TSLastDetail;
friend class TSPartInstance;
/// Base class for all renderable objects, including mesh objects and decal objects.
///
/// An ObjectInstance points to the renderable items in the shape...
struct ObjectInstance
{
virtual ~ObjectInstance() {}
/// this needs to be set before using an objectInstance...tells us where to
/// look for the transforms...gets set be shape instance 'setStatics' method
const Vector<MatrixF> *mTransforms;
S32 nodeIndex;
/// Gets the transform of this object
inline const MatrixF& getTransform() const
{
return nodeIndex < 0 ? MatrixF::Identity : (*mTransforms)[ nodeIndex ];
}
/// @name Render Functions
/// @{
/// Render! This draws the base-textured object.
virtual void render( S32 objectDetail, TSMaterialList *, const TSRenderState &rdata, F32 alpha );
/// @}
/// @name Collision Routines
/// @{
virtual bool buildPolyList( S32 objectDetail, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials );
virtual bool getFeatures( S32 objectDetail, const MatrixF &mat, const Point3F &n, ConvexFeature *feature, U32 &surfaceKey );
virtual void support( S32 od, const Point3F &v, F32 *currMaxDP, Point3F *currSupport );
virtual bool buildPolyListOpcode( S32 objectDetail, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials );
virtual bool castRayOpcode( S32 objectDetail, const Point3F &start, const Point3F &end, RayInfo *info, TSMaterialList *materials );
virtual bool buildConvexOpcode( const MatrixF &mat, S32 objectDetail, const Box3F &bounds, Convex *c, Convex *list );
/// Ray cast for collision detection
virtual bool castRay( S32 objectDetail, const Point3F &start, const Point3F &end, RayInfo *info, TSMaterialList* materials ) = 0;
/// @}
};
/// These are set up by default based on shape data
struct MeshObjectInstance : ObjectInstance
{
TSMesh * const * meshList; ///< one mesh per detail level... Null entries allowed.
const TSObject * object;
S32 frame;
S32 matFrame;
F32 visible;
/// If true this mesh is forced to be hidden
/// regardless of the animation state.
bool forceHidden;
TSVertexBufferHandle mVertexBuffer;
GFXPrimitiveBufferHandle mPrimitiveBuffer;
/// The time at which this mesh
/// was last rendered.
U32 mLastTime;
MeshObjectInstance();
virtual ~MeshObjectInstance() {}
void render( S32 objectDetail, TSMaterialList *, const TSRenderState &rdata, F32 alpha );
/// Gets the mesh with specified detail level
TSMesh * getMesh(S32 num) const { return num<object->numMeshes ? *(meshList+num) : NULL; }
/// @name Collision Routines
/// @{
bool buildPolyList( S32 objectDetail, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials );
bool getFeatures( S32 objectDetail, const MatrixF &mat, const Point3F &n, ConvexFeature *feature, U32 &surfaceKey );
void support( S32 od, const Point3F &v, F32 *currMaxDP, Point3F *currSupport );
bool castRay( S32 objectDetail, const Point3F &start, const Point3F &end, RayInfo *info, TSMaterialList *materials );
bool castRayRendered( S32 objectDetail, const Point3F &start, const Point3F &end, RayInfo *info, TSMaterialList *materials );
bool buildPolyListOpcode( S32 objectDetail, AbstractPolyList *polyList, const Box3F &box, TSMaterialList* materials );
bool castRayOpcode( S32 objectDetail, const Point3F &start, const Point3F &end, RayInfo *info, TSMaterialList *materials );
bool buildConvexOpcode( const MatrixF &mat, S32 objectDetail, const Box3F &bounds, Convex *c, Convex *list );
/// @}
};
protected:
struct TSCallbackRecord
{
TSCallback * callback;
S32 nodeIndex;
};
//-------------------------------------------------------------------------------------
// Lists used for storage of transforms, nodes, objects, etc...
//-------------------------------------------------------------------------------------
public:
Vector<MeshObjectInstance> mMeshObjects;
/// storage space for node transforms
Vector<MatrixF> mNodeTransforms;
/// @name Reference Transform Vectors
/// unused until first transition
/// @{
Vector<Quat16> mNodeReferenceRotations;
Vector<Point3F> mNodeReferenceTranslations;
Vector<F32> mNodeReferenceUniformScales;
Vector<Point3F> mNodeReferenceScaleFactors;
Vector<Quat16> mNodeReferenceArbitraryScaleRots;
/// @}
/// @name Workspace for Node Transforms
/// @{
static Vector<QuatF> smNodeCurrentRotations;
static Vector<Point3F> smNodeCurrentTranslations;
static Vector<F32> smNodeCurrentUniformScales;
static Vector<Point3F> smNodeCurrentAlignedScales;
static Vector<TSScale> smNodeCurrentArbitraryScales;
static Vector<MatrixF> smNodeLocalTransforms;
static TSIntegerSet smNodeLocalTransformDirty;
/// @}
/// @name Threads
/// keep track of who controls what on currently animating shape
/// @{
static Vector<TSThread*> smRotationThreads;
static Vector<TSThread*> smTranslationThreads;
static Vector<TSThread*> smScaleThreads;
/// @}
TSMaterialList* mMaterialList; ///< by default, points to hShape material list
//-------------------------------------------------------------------------------------
// Misc.
//-------------------------------------------------------------------------------------
protected:
/// @name Ground Transform Data
/// @{
MatrixF mGroundTransform;
TSThread * mGroundThread;
/// @}
bool mScaleCurrentlyAnimated;
S32 mCurrentDetailLevel;
/// 0-1, how far along from current to next (higher) detail level...
///
/// 0=at this dl, 1=at higher detail level, where higher means bigger size on screen
/// for dl=0, we use twice detail level 0's size as the size of the "next" dl
F32 mCurrentIntraDetailLevel;
/// This is only valid when the instance was created from
/// a resource. Else it is null.
Resource<TSShape> mShapeResource;
/// This should always point to a valid shape and should
/// equal mShapeResource if it was created from a resource.
TSShape *mShape;
bool mOwnMaterialList; ///< Does this own the material list pointer?
bool mAlphaAlways;
F32 mAlphaAlwaysValue;
bool mUseOverrideTexture;
U32 debrisRefCount;
// the threads...
Vector<TSThread*> mThreadList;
Vector<TSThread*> mTransitionThreads;
/// @name Transition nodes
/// keep track of nodes that are involved in a transition
///
/// @note this only tracks nodes we're transitioning from...
/// nodes we're transitioning to are implicitly handled
/// (i.e., we don't need to keep track of them)
/// @{
TSIntegerSet mTransitionRotationNodes;
TSIntegerSet mTransitionTranslationNodes;
TSIntegerSet mTransitionScaleNodes;
/// @}
/// keep track of nodes with animation restrictions put on them
TSIntegerSet mMaskRotationNodes;
TSIntegerSet mMaskPosXNodes;
TSIntegerSet mMaskPosYNodes;
TSIntegerSet mMaskPosZNodes;
TSIntegerSet mDisableBlendNodes;
TSIntegerSet mHandsOffNodes; ///< Nodes that aren't animated through threads automatically
TSIntegerSet mCallbackNodes;
// node callbacks
Vector<TSCallbackRecord> mNodeCallbacks;
/// state variables
U32 mTriggerStates;
bool initGround();
void addPath(TSThread * gt, F32 start, F32 end, MatrixF * mat = NULL);
public:
TSShape* getShape() const { return mShape; }
TSMaterialList* getMaterialList() const { return mMaterialList; }
/// Set the material list without taking ownership.
/// @see cloneMaterialList
void setMaterialList( TSMaterialList *matList );
/// Call this to own the material list -- i.e., we'll make a copy of the
/// currently set material list and be responsible for deleting it. You
/// can pass an optional feature set for initializing the cloned materials.
void cloneMaterialList( const FeatureSet *features = NULL );
/// Initializes or re-initializes the material list with
/// an optional feature set.
void initMaterialList( const FeatureSet *features = NULL );
bool ownMaterialList() const { return mOwnMaterialList; }
/// Get the number of material targets in this shape instance
S32 getTargetCount() const
{
if ( mOwnMaterialList )
return getMaterialList()->size();
else
return getShape()->getTargetCount();
}
/// Get the indexed material target (may differ from the base TSShape material
/// list if this instance has been reskinned).
const String& getTargetName( S32 mapToNameIndex ) const
{
if ( mOwnMaterialList )
{
if ( mapToNameIndex < 0 || mapToNameIndex >= getMaterialList()->size() )
return String::EmptyString;
return getMaterialList()->getMaterialName( mapToNameIndex );
}
else
{
return getShape()->getTargetName( mapToNameIndex );
}
}
void reSkin( String newBaseName, String oldBaseName = String::EmptyString );
enum
{
MaskNodeRotation = 0x01,
MaskNodePosX = 0x02,
MaskNodePosY = 0x04,
MaskNodePosZ = 0x08,
MaskNodeBlend = 0x10,
MaskNodeAll = MaskNodeRotation|MaskNodePosX|MaskNodePosY|MaskNodePosZ|MaskNodeBlend,
MaskNodeAllButBlend = MaskNodeRotation|MaskNodePosX|MaskNodePosY|MaskNodePosZ,
MaskNodeAllButRotation = MaskNodePosX|MaskNodePosY|MaskNodePosZ|MaskNodeBlend,
MaskNodeAllButPosX = MaskNodeRotation|MaskNodePosY|MaskNodePosZ|MaskNodeBlend,
MaskNodeAllButPosY = MaskNodeRotation|MaskNodePosX|MaskNodePosZ|MaskNodeBlend,
MaskNodeAllButPosZ = MaskNodeRotation|MaskNodePosX|MaskNodePosY|MaskNodeBlend,
MaskNodeHandsOff = 0x20, ///< meaning, don't even set to default, programmer controls it (blend still applies)
MaskNodeCallback = 0x40 ///< meaning, get local transform via callback function (see setCallback)
///< callback data2 is node index, callback return value is pointer to local transform
///< Note: won't get this callback everytime you animate...application responsibility
///< to make sure matrix pointer continues to point to valid and updated local transform
};
/// @name Node Masking
/// set node masking...
/// @{
void setNodeAnimationState(S32 nodeIndex, U32 animationState, TSCallback * callback = NULL);
U32 getNodeAnimationState(S32 nodeIndex);
/// @}
/// @name Trigger states
/// check trigger value
/// @{
bool getTriggerState(U32 stateNum, bool clearState = true);
void setTriggerState(U32 stateNum, bool on);
void setTriggerStateBit(U32 stateBit, bool on);
/// @}
/// @name Debris Management
/// @{
void incDebrisRefCount() { ++debrisRefCount; }
void decDebrisRefCount() { debrisRefCount > 0 ? --debrisRefCount : 0; }
U32 getDebrisRefCount() const { return debrisRefCount; }
/// @}
/// @name AlphaAlways
/// AlphaAlways allows the entire model to become translucent at the same value
/// @{
void setAlphaAlways(F32 value) { mAlphaAlways = (value<0.99f); mAlphaAlwaysValue = value; }
F32 getAlphaAlwaysValue() const { return mAlphaAlways ? mAlphaAlwaysValue : 1.0f; }
bool getAlphaAlways() const { return mAlphaAlways; }
/// @}
//-------------------------------------------------------------------------------------
// private methods for setting up and affecting animation
//-------------------------------------------------------------------------------------
private:
/// @name Private animation methods
/// These are private methods for setting up and affecting animation
/// @{
void sortThreads();
void updateTransitions();
void handleDefaultScale(S32 a, S32 b, TSIntegerSet & scaleBeenSet);
void updateTransitionNodeTransforms(TSIntegerSet& transitionNodes);
void handleTransitionNodes(S32 a, S32 b);
void handleNodeScale(S32 a, S32 b);
void handleAnimatedScale(TSThread *, S32 a, S32 b, TSIntegerSet &);
void handleMaskedPositionNode(TSThread *, S32 nodeIndex, S32 offset);
void handleBlendSequence(TSThread *, S32 a, S32 b);
void checkScaleCurrentlyAnimated();
/// @}
//-------------------------------------------------------------------------------------
// animate, render, & detail control
//-------------------------------------------------------------------------------------
public:
struct RenderData
{
MeshObjectInstance* currentObjectInstance;
S32 detailLevel;
S32 materialIndex;
const Point3F * objectScale;
};
/// Scale pixel size by this amount when selecting
/// detail levels.
static F32 smDetailAdjust;
/// If this is set to a positive pixel value shapes
/// with a smaller pixel size than this will skip
/// rendering entirely.
static F32 smSmallestVisiblePixelSize;
/// never choose detail level number below this value (except if
/// only way to get a visible detail)
static S32 smNumSkipRenderDetails;
/// For debugging / metrics.
static F32 smLastScreenErrorTolerance;
static F32 smLastScaledDistance;
static F32 smLastPixelSize;
/// Debugging
/// @{
/// Renders the vertex normals assuming the GFX state
/// is setup for rendering in model space.
void renderDebugNormals( F32 normalScalar, S32 dl );
/// Render all node transforms as small axis gizmos. It is recommended
/// that prior to calling this, shapeInstance::animate is called so that
/// nodes are in object space and that the GFX state is setup for
/// rendering in model space.
void renderDebugNodes();
/// Print mesh data to the console, valid String parameters
/// are Visible, Hidden, or All.
void listMeshes( const String &state ) const;
/// @}
virtual void render( const TSRenderState &rdata );
virtual void render( const TSRenderState &rdata, S32 dl, F32 intraDL = 0.0f );
void animate() { animate( mCurrentDetailLevel ); }
void animate(S32 dl);
void animateNodes(S32 ss);
void animateVisibility(S32 ss);
void animateFrame(S32 ss);
void animateMatFrame(S32 ss);
void animateSubtrees(bool forceFull = true);
void animateNodeSubtrees(bool forceFull = true);
/// Sets the 'forceHidden' state on the named mesh.
/// @see MeshObjectInstance::forceHidden
void setMeshForceHidden( const char *meshName, bool hidden );
/// Sets the 'forceHidden' state on a mesh.
/// @see MeshObjectInstance::forceHidden
void setMeshForceHidden( S32 meshIndex, bool hidden );
/// @name Animation Scale
/// Query about animated scale
/// @{
bool animatesScale() { return (mShape->mFlags & TSShape::AnyScale) != 0; }
bool animatesUniformScale() { return (mShape->mFlags & TSShape::UniformScale) != 0; }
bool animatesAlignedScale() { return (mShape->mFlags & TSShape::AlignedScale) != 0; }
bool animatesArbitraryScale() { return (mShape->mFlags & TSShape::ArbitraryScale) != 0; }
bool scaleCurrentlyAnimated() { return mScaleCurrentlyAnimated; }
/// @}
//
bool inTransition() { return !mTransitionThreads.empty(); }
/// @name Ground Transforms
/// The animator of a model can make the bounding box
/// animate along with the object. Doing so will move the object with the bounding box.
/// The ground transform turns the world bounding box into the post-animation bounding box
/// when such a technique is used. However, few models actually use this technique.
/// @{
void animateGround(); ///< clears previous ground transform
MatrixF & getGroundTransform() { return mGroundTransform; }
void deltaGround(TSThread *, F32 start, F32 end, MatrixF * mat = NULL);
void deltaGround1(TSThread *, F32 start, F32 end, MatrixF& mat);
/// @}
U32 getNumDetails() const { return mShape ? mShape->details.size() : 0; }
S32 getCurrentDetail() const { return mCurrentDetailLevel; }
F32 getCurrentIntraDetail() const { return mCurrentIntraDetailLevel; }
void setCurrentDetail( S32 dl, F32 intraDL = 1.0f );
/// Helper function which internally calls setDetailFromDistance.
S32 setDetailFromPosAndScale( const SceneRenderState *state,
const Point3F &pos,
const Point3F &scale );
/// Selects the current detail level using the scaled
/// distance between your object and the camera.
///
/// @see TSShape::Detail.
S32 setDetailFromDistance( const SceneRenderState *state, F32 scaledDist );
/// Sets the current detail level using the legacy screen error metric.
S32 setDetailFromScreenError( F32 errorTOL );
enum
{
TransformDirty = BIT(0),
VisDirty = BIT(1),
FrameDirty = BIT(2),
MatFrameDirty = BIT(3),
ThreadDirty = BIT(4),
AllDirtyMask = TransformDirty | VisDirty | FrameDirty | MatFrameDirty | ThreadDirty
};
U32 * mDirtyFlags;
void setDirty(U32 dirty);
void clearDirty(U32 dirty);
//-------------------------------------------------------------------------------------
// collision interface routines
//-------------------------------------------------------------------------------------
public:
bool buildPolyList(AbstractPolyList *, S32 dl);
bool getFeatures(const MatrixF& mat, const Point3F& n, ConvexFeature*, S32 dl);
bool castRay(const Point3F & start, const Point3F & end, RayInfo *,S32 dl);
bool castRayRendered(const Point3F & start, const Point3F & end, RayInfo *,S32 dl);
bool quickLOS(const Point3F & start, const Point3F & end, S32 dl) { return castRay(start,end,NULL,dl); }
Point3F support(const Point3F & v, S32 dl);
void computeBounds(S32 dl, Box3F & bounds); ///< uses current transforms to compute bounding box around a detail level
///< see like named method on shape if you want to use default transforms
bool buildPolyListOpcode( S32 dl, AbstractPolyList *polyList, const Box3F &box );
bool castRayOpcode( S32 objectDetail, const Point3F & start, const Point3F & end, RayInfo *);
bool buildConvexOpcode( const MatrixF &objMat, const Point3F &objScale, S32 objectDetail, const Box3F &bounds, Convex *c, Convex *list );
//-------------------------------------------------------------------------------------
// Thread Control
//-------------------------------------------------------------------------------------
/// @name Thread Control
/// Threads! In order to animate an object, first you need to have an animation in the object.
/// Then, you need to get the TSShape of the object:
/// @code
/// TSShape* shape = mShapeInstance->getShape());
/// @endcode
/// Next, get the sequence and store::
/// @code
/// S32 seq = shape->findSequence("foo"));
/// @endcode
/// Create a new thread (if needed):
/// @code
/// TSThread* thread = mShapeInstance->addThread();
/// @endcode
/// Finally, set the position in the sequence:
/// @code
/// mShapeInstance->setSequence(thread, seq, 0)
/// @endcode
/// @{
public:
TSThread * addThread(); ///< Create a new thread
TSThread * getThread(S32 threadNumber); ///< @note threads can change order, best to hold
///< onto a thread from the start
void destroyThread(TSThread * thread); ///< Destroy a thread!
U32 threadCount(); ///< How many threads are there?
void setSequence(TSThread *, S32 seq, F32 pos);///< Get the thread a sequence
/// Transition to a sequence
void transitionToSequence(TSThread *, S32 seq, F32 pos, F32 duration, bool continuePlay);
void clearTransition(TSThread *); ///< Stop transitions
U32 getSequence(TSThread *); ///< Get the sequence of the thread
void setBlendEnabled(TSThread *, bool blendOn);///< Set whether or not the thread will blend
bool getBlendEnabled(TSThread *); ///< Does this thread blend?
void setPriority(TSThread *, F32 priority); ///< Set thread priority
F32 getPriority(TSThread * thread); ///< Get thread priority
F32 getTime(TSThread * thread); ///< Get how long the thread has been playing
F32 getPos(TSThread * thread); ///< Get the position in the thread
void setTime(TSThread * thread, F32 time); ///< Set how long into the thread to use
void setPos(TSThread * thread, F32 pos); ///< Set the position of the thread
bool isInTransition(TSThread * thread); ///< Is this thread in transition?
F32 getTimeScale(TSThread * thread); ///< Get the time scale of the thread
void setTimeScale(TSThread * thread, F32); ///< Set the time scale of the thread
F32 getDuration(TSThread * thread); ///< Get the duration of the thread
F32 getScaledDuration(TSThread * thread); ///< Get the duration of the thread with the scale factored in
S32 getKeyframeCount(TSThread * thread); ///< Get the number of keyframes
S32 getKeyframeNumber(TSThread * thread); ///< Get which keyframe the thread is on
/// Set which keyframe the thread is on
void setKeyframeNumber(TSThread * thread, S32 kf);
void advanceTime(F32 delta, TSThread *); ///< advance time on a particular thread
void advanceTime(F32 delta); ///< advance time on all threads
void advancePos(F32 delta, TSThread *); ///< advance pos on a particular thread
void advancePos(F32 delta); ///< advance pos on all threads
/// @}
//-------------------------------------------------------------------------------------
// constructors, destructors, initialization, io
//-------------------------------------------------------------------------------------
TSShapeInstance( const Resource<TSShape> & shape, bool loadMaterials = true);
TSShapeInstance( TSShape * pShape, bool loadMaterials = true);
~TSShapeInstance();
void buildInstanceData(TSShape *, bool loadMaterials);
void initNodeTransforms();
void initMeshObjects();
void dump(Stream &);
void dumpNode(Stream &, S32 level, S32 nodeIndex, Vector<S32> & detailSizes);
void *mData; ///< available for use by app...initialized to 0
void prepCollision();
};
//-------------------------------------------------------------------------------------
// Thread class
//-------------------------------------------------------------------------------------
/// 3space animation thread.
///
/// An animation thread: runtime data associated with a single sequence that is
/// running (or two sequences if in transition between them).
///
/// A shape instance can have multiple threads running. When multiple threads are running,
/// which thread/sequence controls which node or object is determined based
/// on priority of the sequence.
///
/// @note all thread data and methods are private (but TSShapeInstance is a friend).
/// Users should treat thread pointers like keys -- they are used to ID
/// the thread when interfacing with the shape, but are not manipulated
/// by anything but the TSShapeInstance. See "Thread control" methods
/// for more info on controlling threads.
class TSThread
{
friend class TSShapeInstance;
S32 priority;
TSShapeInstance * mShapeInstance; ///< Instance of the shape that this thread animates
S32 sequence; ///< Sequence this thread will perform
F32 pos;
F32 timeScale; ///< How fast to play through the sequence
S32 keyNum1; ///< Keyframe at or before current position
S32 keyNum2; ///< Keyframe at or after current position
F32 keyPos;
bool blendDisabled; ///< Blend with other sequences?
/// if in transition...
struct TransitionData
{
bool inTransition;
F32 duration;
F32 pos;
F32 direction;
F32 targetScale; ///< time scale for sequence we are transitioning to (during transition only)
///< this is either 1 or 0 (if 1 target sequence plays as we transition, if 0 it doesn't)
TSIntegerSet oldRotationNodes; ///< nodes controlled by this thread pre-transition
TSIntegerSet oldTranslationNodes; ///< nodes controlled by this thread pre-transition
TSIntegerSet oldScaleNodes; ///< nodes controlled by this thread pre-transition
U32 oldSequence; ///< sequence that was set before transition began
F32 oldPos; ///< position of sequence before transition began
} transitionData;
struct
{
F32 start;
F32 end;
S32 loop;
} path;
bool makePath;
/// given a position on the thread, choose correct keyframes
/// slight difference between one-shot and cyclic sequences -- see comments below for details
void selectKeyframes(F32 pos, const TSSequence * seq, S32 * k1, S32 * k2, F32 * kpos);
void getGround(F32 p, MatrixF * pMat);
/// @name Triggers
/// Triggers are used to do something once a certain animation point has been reached.
///
/// For example, when the player's foot animation hits the ground, a foot puff and
/// foot print are triggered from the thread.
///
/// These are called by advancePos()
/// @{
void animateTriggers();
void activateTriggers(F32 a, F32 b);
/// @}
TSThread(TSShapeInstance*);
TSThread() {}
void setSequence(S32 seq, F32 pos);
void transitionToSequence(S32 seq, F32 pos, F32 duration, bool continuePlay);
void advanceTime(F32 delta);
void advancePos(F32 delta);
F32 getTime();
F32 getPos();
void setTime(F32);
void setPos(F32);
bool isInTransition();
F32 getTimeScale();
void setTimeScale(F32);
F32 getDuration();
F32 getScaledDuration();
S32 getKeyframeCount();
S32 getKeyframeNumber();
void setKeyframeNumber(S32 kf);
public:
TSShapeInstance * getShapeInstance() { return mShapeInstance; }
bool hasSequence() const { return sequence >= 0; }
U32 getSeqIndex() const { return sequence; }
const TSSequence* getSequence() const { return &(mShapeInstance->mShape->sequences[sequence]); }
const String& getSequenceName() const { return mShapeInstance->mShape->getSequenceName(sequence); }
S32 operator<(const TSThread &) const;
};
typedef TSShapeInstance::ObjectInstance TSObjectInstance;
#endif // _TSSHAPEINSTANCE_H_

View file

@ -0,0 +1,886 @@
//-----------------------------------------------------------------------------
// 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 "core/strings/stringFunctions.h"
#include "core/util/endian.h"
#include "ts/tsShapeInstance.h"
//-------------------------------------------------
// put old skins into object list
//-------------------------------------------------
void TSShape::fixupOldSkins(S32 numMeshes, S32 numSkins, S32 numDetails, S32 * detailFirstSkin, S32 * detailNumSkins)
{
#if !defined(TORQUE_MAX_LIB)
// this method not necessary in exporter, and a couple lines won't compile for exporter
if (!objects.address() || !meshes.address() || !numSkins)
// not ready for this yet, will catch it on the next pass
return;
S32 numObjects = objects.size();
TSObject * newObjects = objects.address() + objects.size();
TSSkinMesh ** skins = (TSSkinMesh**)&meshes[numMeshes];
Vector<TSSkinMesh*> skinsCopy;
// Note: newObjects has as much free space as we need, so we just need to keep track of the
// number of objects we use and then update objects.size
S32 numSkinObjects = 0;
S32 skinsUsed = 0;
S32 emptySkins = 0;
S32 i;
for (i=0; i<numSkins; i++)
if (skins[i]==NULL)
emptySkins++; // probably never, but just in case
while (skinsUsed<numSkins-emptySkins)
{
TSObject & object = newObjects[numSkinObjects++];
objects.increment();
object.nameIndex = 0; // no name
object.numMeshes = 0;
object.startMeshIndex = numMeshes + skinsCopy.size();
object.nodeIndex = -1;
object.nextSibling = -1;
for (S32 dl=0; dl<numDetails; dl++)
{
// find one mesh per detail to add to this object
// don't really need to be versions of the same object
i = 0;
while (i<detailFirstSkin[dl] || detailFirstSkin[dl]<0)
i++;
for (; i<numSkins && i<detailFirstSkin[dl]+detailNumSkins[dl]; i++)
{
if (skins[i])
{
// found an unused skin... copy it to skinsCopy and set to NULL
skinsCopy.push_back(skins[i]);
skins[i]=NULL;
object.numMeshes++;
skinsUsed++;
break;
}
}
if (i==numSkins || i==detailFirstSkin[dl]+detailNumSkins[dl])
{
skinsCopy.push_back(NULL);
object.numMeshes++;
}
}
// exit above loop with one skin per detail...despose of trailing null meshes
while (!skinsCopy.empty() && skinsCopy.last()==NULL)
{
skinsCopy.decrement();
object.numMeshes--;
}
// if no meshes, don't need object
if (!object.numMeshes)
{
objects.decrement();
numSkinObjects--;
}
}
dMemcpy(skins,skinsCopy.address(),skinsCopy.size()*sizeof(TSSkinMesh*));
if (subShapeFirstObject.size()==1)
// as long as only one subshape, we'll now be rendered
subShapeNumObjects[0] += numSkinObjects;
// now for something ugly -- we've added somoe objects to hold the skins...
// now we have to add default states for those objects
// we also have to increment base states on all the sequences that are loaded
dMemmove(objectStates.address()+numObjects+numSkinObjects,objectStates.address()+numObjects,(objectStates.size()-numObjects)*sizeof(ObjectState));
for (i=numObjects; i<numObjects+numSkinObjects; i++)
{
objectStates[i].vis=1.0f;
objectStates[i].frameIndex=0;
objectStates[i].matFrameIndex=0;
}
for (i=0;i<sequences.size();i++)
{
sequences[i].baseObjectState += numSkinObjects;
}
#endif
}
//-------------------------------------------------
// some macros used for read/write
//-------------------------------------------------
// write a vector of structs (minus the first 'm')
#define writeVectorStructMinus(a,m) \
{\
s->write(a.size() - m); \
for (S32 i=m;i<a.size();i++) \
a[i].write(s); \
}
// write a vector of simple types (minus the first 'm')
#define writeVectorSimpleMinus(a,m) \
{\
s->write(a.size() - m); \
for (S32 i=m;i<a.size();i++) \
s->write(a[i]); \
}
// same as above with m=0
#define writeVectorStruct(a) writeVectorStructMinus(a,0)
#define writeVectorSimple(a) writeVectorSimpleMinus(a,0)
// read a vector of structs -- over-writing any existing data
#define readVectorStruct(a) \
{ \
S32 sz; \
s->read(&sz); \
a.setSize(sz); \
for (S32 i=0;i<sz;i++) \
a[i].read(s); \
}
// read a vector of simple types -- over-writing any existing data
#define readVectorSimple(a) \
{ \
S32 sz; \
s->read(&sz); \
a.setSize(sz); \
for (S32 i=0;i<sz;i++) \
s->read(&a[i]); \
}
// read a vector of structs -- append to any existing data
#define appendVectorStruct(a) \
{ \
S32 sz; \
S32 oldSz = a.size(); \
s->read(&sz); \
a.setSize(oldSz + sz); \
for (S32 i=0;i<sz;i++) \
a[i + oldSz].read(s); \
}
// read a vector of simple types -- append to any existing data
#define appendVectorSimple(a) \
{ \
S32 sz; \
S32 oldSz = a.size(); \
s->read(&sz); \
a.setSize(oldSz + sz); \
for (S32 i=0;i<sz;i++) \
s->read(&a[i + oldSz]); \
}
//-------------------------------------------------
// export all sequences
//-------------------------------------------------
void TSShape::exportSequences(Stream * s)
{
// write version
s->write(smVersion);
S32 i,sz;
// write node names
// -- this is how we will map imported sequence nodes to shape nodes
sz = nodes.size();
s->write(sz);
for (i=0;i<nodes.size();i++)
writeName(s,nodes[i].nameIndex);
// legacy write -- write zero objects, don't pretend to support object export anymore
s->write(0);
// on import, we will need to adjust keyframe data based on number of
// nodes/objects in this shape...number of nodes can be inferred from
// above, but number of objects cannot be. Write that quantity here:
s->write(objects.size());
// write node states -- skip default node states
s->write(nodeRotations.size());
for (i=0;i<nodeRotations.size();i++)
{
s->write(nodeRotations[i].x);
s->write(nodeRotations[i].y);
s->write(nodeRotations[i].z);
s->write(nodeRotations[i].w);
}
s->write(nodeTranslations.size());
for (i=0;i<nodeTranslations.size(); i++)
{
s->write(nodeTranslations[i].x);
s->write(nodeTranslations[i].y);
s->write(nodeTranslations[i].z);
}
s->write(nodeUniformScales.size());
for (i=0;i<nodeUniformScales.size();i++)
s->write(nodeUniformScales[i]);
s->write(nodeAlignedScales.size());
for (i=0;i<nodeAlignedScales.size();i++)
{
s->write(nodeAlignedScales[i].x);
s->write(nodeAlignedScales[i].y);
s->write(nodeAlignedScales[i].z);
}
s->write(nodeArbitraryScaleRots.size());
for (i=0;i<nodeArbitraryScaleRots.size();i++)
{
s->write(nodeArbitraryScaleRots[i].x);
s->write(nodeArbitraryScaleRots[i].y);
s->write(nodeArbitraryScaleRots[i].z);
s->write(nodeArbitraryScaleRots[i].w);
}
for (i=0;i<nodeArbitraryScaleFactors.size();i++)
{
s->write(nodeArbitraryScaleFactors[i].x);
s->write(nodeArbitraryScaleFactors[i].y);
s->write(nodeArbitraryScaleFactors[i].z);
}
s->write(groundTranslations.size());
for (i=0;i<groundTranslations.size();i++)
{
s->write(groundTranslations[i].x);
s->write(groundTranslations[i].y);
s->write(groundTranslations[i].z);
}
for (i=0;i<groundRotations.size();i++)
{
s->write(groundRotations[i].x);
s->write(groundRotations[i].y);
s->write(groundRotations[i].z);
s->write(groundRotations[i].w);
}
// write object states -- legacy..no object states
s->write((S32)0);
// write sequences
s->write(sequences.size());
for (i=0;i<sequences.size();i++)
{
Sequence & seq = sequences[i];
// first write sequence name
writeName(s,seq.nameIndex);
// now write the sequence itself
seq.write(s,false); // false --> don't write name index
}
// write out all the triggers...
s->write(triggers.size());
for (i=0; i<triggers.size(); i++)
{
s->write(triggers[i].state);
s->write(triggers[i].pos);
}
}
//-------------------------------------------------
// export a single sequence
//-------------------------------------------------
void TSShape::exportSequence(Stream * s, const TSShape::Sequence& seq, bool saveOldFormat)
{
S32 currentVersion = smVersion;
if ( saveOldFormat )
smVersion = 24;
// write version
s->write(smVersion);
// write node names
s->write( nodes.size() );
for ( S32 i = 0; i < nodes.size(); i++ )
writeName( s, nodes[i].nameIndex );
// legacy write -- write zero objects, don't pretend to support object export anymore
s->write( (S32)0 );
// on import, we will need to adjust keyframe data based on number of
// nodes/objects in this shape...number of nodes can be inferred from
// above, but number of objects cannot be. Write that quantity here:
s->write( objects.size() );
// write node states -- skip default node states
S32 count = seq.rotationMatters.count() * seq.numKeyframes;
s->write( count );
for ( S32 i = seq.baseRotation; i < seq.baseRotation + count; i++ )
{
s->write( nodeRotations[i].x );
s->write( nodeRotations[i].y );
s->write( nodeRotations[i].z );
s->write( nodeRotations[i].w );
}
count = seq.translationMatters.count() * seq.numKeyframes;
s->write( count );
for ( S32 i = seq.baseTranslation; i < seq.baseTranslation + count; i++ )
{
s->write( nodeTranslations[i].x );
s->write( nodeTranslations[i].y );
s->write( nodeTranslations[i].z );
}
count = seq.scaleMatters.count() * seq.numKeyframes;
if ( seq.animatesUniformScale() )
{
s->write( count );
for ( S32 i = seq.baseScale; i < seq.baseScale + count; i++ )
s->write( nodeUniformScales[i] );
}
else
s->write( (S32)0 );
if ( seq.animatesAlignedScale() )
{
s->write( count );
for ( S32 i = seq.baseScale; i < seq.baseScale + count; i++ )
{
s->write( nodeAlignedScales[i].x );
s->write( nodeAlignedScales[i].y );
s->write( nodeAlignedScales[i].z );
}
}
else
s->write( (S32)0 );
if ( seq.animatesArbitraryScale() )
{
s->write( count );
for ( S32 i = seq.baseScale; i < seq.baseScale + count; i++ )
{
s->write( nodeArbitraryScaleRots[i].x );
s->write( nodeArbitraryScaleRots[i].y );
s->write( nodeArbitraryScaleRots[i].z );
s->write( nodeArbitraryScaleRots[i].w );
}
for ( S32 i = seq.baseScale; i < seq.baseScale + count; i++ )
{
s->write( nodeArbitraryScaleFactors[i].x );
s->write( nodeArbitraryScaleFactors[i].y );
s->write( nodeArbitraryScaleFactors[i].z );
}
}
else
s->write( (S32)0 );
s->write( seq.numGroundFrames );
for ( S32 i = seq.firstGroundFrame; i < seq.firstGroundFrame + seq.numGroundFrames; i++ )
{
s->write( groundTranslations[i].x );
s->write( groundTranslations[i].y );
s->write( groundTranslations[i].z );
}
for ( S32 i = seq.firstGroundFrame; i < seq.firstGroundFrame + seq.numGroundFrames; i++ )
{
s->write( groundRotations[i].x );
s->write( groundRotations[i].y );
s->write( groundRotations[i].z );
s->write( groundRotations[i].w );
}
// write object states -- legacy..no object states
s->write( (S32)0 );
// write the sequence
s->write( (S32)1 );
writeName( s, seq.nameIndex );
{
// Write a copy of the sequence with all offsets set to 0
TSShape::Sequence tmpSeq(seq);
tmpSeq.baseDecalState = 0;
tmpSeq.baseObjectState = 0;
tmpSeq.baseTranslation = 0;
tmpSeq.baseRotation = 0;
tmpSeq.baseScale = 0;
tmpSeq.firstGroundFrame = 0;
tmpSeq.firstTrigger = 0;
tmpSeq.write( s, false );
}
// write the sequence triggers
s->write( seq.numTriggers );
for ( S32 i = seq.firstTrigger; i < seq.firstTrigger + seq.numTriggers; i++ )
{
s->write( triggers[i].state );
s->write( triggers[i].pos );
}
smVersion = currentVersion;
}
//-------------------------------------------------
// import sequences into existing shape
//-------------------------------------------------
bool TSShape::importSequences(Stream * s, const String& sequencePath)
{
// write version
s->read(&smReadVersion);
if (smReadVersion>smVersion)
{
// error -- don't support future version yet :>
Con::errorf(ConsoleLogEntry::General,
"Sequence import failed: shape exporter newer than running executable.");
return false;
}
if (smReadVersion<19)
{
// error -- don't support future version yet :>
Con::errorf(ConsoleLogEntry::General,
"Sequence import failed: deprecated version (%i).",smReadVersion);
return false;
}
Vector<S32> nodeMap; // node index of each node from imported sequences
Vector<S32> objectMap; // object index of objects from imported sequences
VECTOR_SET_ASSOCIATION(nodeMap);
VECTOR_SET_ASSOCIATION(objectMap);
S32 i,sz;
// read node names
// -- this is how we will map imported sequence nodes to our nodes
s->read(&sz);
nodeMap.setSize(sz);
for (i=0;i<sz;i++)
{
U32 startSize = names.size();
S32 nameIndex = readName(s,true);
nodeMap[i] = findNode(nameIndex);
if (nodeMap[i] < 0)
{
// node found in sequence but not shape => remove the added node name
if (names.size() != startSize)
{
names.decrement();
if (names.size() != startSize)
Con::errorf(ConsoleLogEntry::General, "TSShape::importSequence: failed to remove unused node correctly for dsq %s.", names[nameIndex].c_str(), sequencePath.c_str());
}
}
}
// read the following size, but won't do anything with it...legacy: was going to support
// import of sequences that animate objects...we don't...
s->read(&sz);
// before reading keyframes, take note of a couple numbers
S32 oldShapeNumObjects;
s->read(&oldShapeNumObjects);
// adjust all the new keyframes
S32 adjNodeRots = smReadVersion<22 ? nodeRotations.size() - nodeMap.size() : nodeRotations.size();
S32 adjNodeTrans = smReadVersion<22 ? nodeTranslations.size() - nodeMap.size() : nodeTranslations.size();
S32 adjGroundStates = smReadVersion<22 ? 0 : groundTranslations.size(); // groundTrans==groundRot
// Read the node states into temporary vectors, then use the
// nodeMap to discard unused transforms and map others to our nodes
Vector<Quat16> seqRotations;
Vector<Point3F> seqTranslations;
Vector<F32> seqUniformScales;
Vector<Point3F> seqAlignedScales;
Vector<Quat16> seqArbitraryScaleRots;
Vector<Point3F> seqArbitraryScaleFactors;
if (smReadVersion>21)
{
s->read(&sz);
seqRotations.setSize(sz);
for (i=0; i < sz; i++)
{
s->read(&seqRotations[i].x);
s->read(&seqRotations[i].y);
s->read(&seqRotations[i].z);
s->read(&seqRotations[i].w);
}
s->read(&sz);
seqTranslations.setSize(sz);
for (i=0; i <sz; i++)
{
s->read(&seqTranslations[i].x);
s->read(&seqTranslations[i].y);
s->read(&seqTranslations[i].z);
}
s->read(&sz);
seqUniformScales.setSize(sz);
for (i = 0; i < sz; i++)
s->read(&seqUniformScales[i]);
s->read(&sz);
seqAlignedScales.setSize(sz);
for (i = 0; i < sz; i++)
{
s->read(&seqAlignedScales[i].x);
s->read(&seqAlignedScales[i].y);
s->read(&seqAlignedScales[i].z);
}
s->read(&sz);
seqArbitraryScaleRots.setSize(sz);
for (i = 0; i <sz; i++)
{
s->read(&seqArbitraryScaleRots[i].x);
s->read(&seqArbitraryScaleRots[i].y);
s->read(&seqArbitraryScaleRots[i].z);
s->read(&seqArbitraryScaleRots[i].w);
}
seqArbitraryScaleFactors.setSize(sz);
for (i = 0; i < sz; i++)
{
s->read(&seqArbitraryScaleFactors[i].x);
s->read(&seqArbitraryScaleFactors[i].y);
s->read(&seqArbitraryScaleFactors[i].z);
}
// ground transforms can be read directly into the shape (none will be
// discarded)
s->read(&sz);
S32 oldSz = groundTranslations.size();
groundTranslations.setSize(sz+oldSz);
for (i=oldSz;i<sz+oldSz;i++)
{
s->read(&groundTranslations[i].x);
s->read(&groundTranslations[i].y);
s->read(&groundTranslations[i].z);
}
groundRotations.setSize(sz+oldSz);
for (i=oldSz;i<sz+oldSz;i++)
{
s->read(&groundRotations[i].x);
s->read(&groundRotations[i].y);
s->read(&groundRotations[i].z);
s->read(&groundRotations[i].w);
}
}
else
{
s->read(&sz);
seqRotations.setSize(sz);
seqTranslations.setSize(sz);
for (i = 0; i < sz; i++)
{
s->read(&seqRotations[i].x);
s->read(&seqRotations[i].y);
s->read(&seqRotations[i].z);
s->read(&seqRotations[i].w);
s->read(&seqTranslations[i].x);
s->read(&seqTranslations[i].y);
s->read(&seqTranslations[i].z);
}
}
// add these object states to our own -- shouldn't be any...assume it
s->read(&sz);
// read sequences
s->read(&sz);
S32 startSeqNum = sequences.size();
for (i=0;i<sz;i++)
{
sequences.increment();
Sequence & seq = sequences.last();
// read name
seq.nameIndex = readName(s,true);
// read the rest of the sequence
seq.read(s,false);
seq.baseRotation = nodeRotations.size();
seq.baseTranslation = nodeTranslations.size();
if (smReadVersion > 21)
{
if (seq.animatesUniformScale())
seq.baseScale = nodeUniformScales.size();
else if (seq.animatesAlignedScale())
seq.baseScale = nodeAlignedScales.size();
else if (seq.animatesArbitraryScale())
seq.baseScale = nodeArbitraryScaleFactors.size();
}
// remap the node matters arrays
S32 j;
TSIntegerSet newTransMembership;
TSIntegerSet newRotMembership;
TSIntegerSet newScaleMembership;
for (j = 0; j < (S32)nodeMap.size(); j++)
{
if (nodeMap[j] < 0)
continue;
if (seq.translationMatters.test(j))
newTransMembership.set(nodeMap[j]);
if (seq.rotationMatters.test(j))
newRotMembership.set(nodeMap[j]);
if (seq.scaleMatters.test(j))
newScaleMembership.set(nodeMap[j]);
}
// resize node transform arrays
nodeTranslations.increment(newTransMembership.count() * seq.numKeyframes);
nodeRotations.increment(newRotMembership.count() * seq.numKeyframes);
if (seq.flags & TSShape::ArbitraryScale)
{
S32 scaleCount = newScaleMembership.count() * seq.numKeyframes;
nodeArbitraryScaleRots.increment(scaleCount);
nodeArbitraryScaleFactors.increment(scaleCount);
}
else if (seq.flags & TSShape::AlignedScale)
nodeAlignedScales.increment(newScaleMembership.count() * seq.numKeyframes);
else
nodeUniformScales.increment(newScaleMembership.count() * seq.numKeyframes);
// remap node transforms from temporary arrays
for (S32 j = 0; j < nodeMap.size(); j++)
{
if (nodeMap[j] < 0)
continue;
if (newTransMembership.test(nodeMap[j]))
{
S32 src = seq.numKeyframes * seq.translationMatters.count(j);
S32 dest = seq.baseTranslation + seq.numKeyframes * newTransMembership.count(nodeMap[j]);
dCopyArray(&nodeTranslations[dest], &seqTranslations[src], seq.numKeyframes);
}
if (newRotMembership.test(nodeMap[j]))
{
S32 src = seq.numKeyframes * seq.rotationMatters.count(j);
S32 dest = seq.baseRotation + seq.numKeyframes * newRotMembership.count(nodeMap[j]);
dCopyArray(&nodeRotations[dest], &seqRotations[src], seq.numKeyframes);
}
if (newScaleMembership.test(nodeMap[j]))
{
S32 src = seq.numKeyframes * seq.scaleMatters.count(j);
S32 dest = seq.baseScale + seq.numKeyframes * newScaleMembership.count(nodeMap[j]);
if (seq.flags & TSShape::ArbitraryScale)
{
dCopyArray(&nodeArbitraryScaleRots[dest], &seqArbitraryScaleRots[src], seq.numKeyframes);
dCopyArray(&nodeArbitraryScaleFactors[dest], &seqArbitraryScaleFactors[src], seq.numKeyframes);
}
else if (seq.flags & TSShape::AlignedScale)
dCopyArray(&nodeAlignedScales[dest], &seqAlignedScales[src], seq.numKeyframes);
else
dCopyArray(&nodeUniformScales[dest], &seqUniformScales[src], seq.numKeyframes);
}
}
seq.translationMatters = newTransMembership;
seq.rotationMatters = newRotMembership;
seq.scaleMatters = newScaleMembership;
// adjust trigger numbers...we'll read triggers after sequences...
seq.firstTrigger += triggers.size();
// finally, adjust ground transform's nodes states
seq.firstGroundFrame += adjGroundStates;
}
if (smReadVersion<22)
{
for (i=startSeqNum; i<sequences.size(); i++)
{
// move ground transform data to ground vectors
Sequence & seq = sequences[i];
S32 oldSz = groundTranslations.size();
groundTranslations.setSize(oldSz+seq.numGroundFrames);
groundRotations.setSize(oldSz+seq.numGroundFrames);
for (S32 j=0;j<seq.numGroundFrames;j++)
{
groundTranslations[j+oldSz] = nodeTranslations[seq.firstGroundFrame+adjNodeTrans+j];
groundRotations[j+oldSz] = nodeRotations[seq.firstGroundFrame+adjNodeRots+j];
}
seq.firstGroundFrame = oldSz;
}
}
// add the new triggers
S32 oldSz = triggers.size();
s->read(&sz);
triggers.setSize(oldSz+sz);
for (S32 i=0; i<sz;i++)
{
s->read(&triggers[i+oldSz].state);
s->read(&triggers[i+oldSz].pos);
}
if (smInitOnRead)
init();
return true;
}
//-------------------------------------------------
// read/write sequence
//-------------------------------------------------
void TSShape::Sequence::read(Stream * s, bool readNameIndex)
{
AssertISV(smReadVersion>=19,"Reading old sequence");
if (readNameIndex)
s->read(&nameIndex);
flags = 0;
if (TSShape::smReadVersion>21)
s->read(&flags);
else
flags=0;
s->read(&numKeyframes);
s->read(&duration);
if (TSShape::smReadVersion<22)
{
bool tmp = false;
s->read(&tmp);
if (tmp)
flags |= Blend;
s->read(&tmp);
if (tmp)
flags |= Cyclic;
s->read(&tmp);
if (tmp)
flags |= MakePath;
}
s->read(&priority);
s->read(&firstGroundFrame);
s->read(&numGroundFrames);
if (TSShape::smReadVersion>21)
{
s->read(&baseRotation);
s->read(&baseTranslation);
s->read(&baseScale);
s->read(&baseObjectState);
s->read(&baseDecalState);
}
else
{
s->read(&baseRotation);
baseTranslation=baseRotation;
s->read(&baseObjectState);
s->read(&baseDecalState);
}
s->read(&firstTrigger);
s->read(&numTriggers);
s->read(&toolBegin);
// now the membership sets:
rotationMatters.read(s);
if (TSShape::smReadVersion<22)
translationMatters=rotationMatters;
else
{
translationMatters.read(s);
scaleMatters.read(s);
}
TSIntegerSet dummy;
dummy.read(s); // DEPRECIATED: Decals
dummy.read(s); // DEPRECIATED: Ifl materials
visMatters.read(s);
frameMatters.read(s);
matFrameMatters.read(s);
dirtyFlags = 0;
if (rotationMatters.testAll() || translationMatters.testAll() || scaleMatters.testAll())
dirtyFlags |= TSShapeInstance::TransformDirty;
if (visMatters.testAll())
dirtyFlags |= TSShapeInstance::VisDirty;
if (frameMatters.testAll())
dirtyFlags |= TSShapeInstance::FrameDirty;
if (matFrameMatters.testAll())
dirtyFlags |= TSShapeInstance::MatFrameDirty;
}
void TSShape::Sequence::write(Stream * s, bool writeNameIndex) const
{
if (writeNameIndex)
s->write(nameIndex);
s->write(flags);
s->write(numKeyframes);
s->write(duration);
s->write(priority);
s->write(firstGroundFrame);
s->write(numGroundFrames);
s->write(baseRotation);
s->write(baseTranslation);
s->write(baseScale);
s->write(baseObjectState);
s->write(baseDecalState);
s->write(firstTrigger);
s->write(numTriggers);
s->write(toolBegin);
// now the membership sets:
rotationMatters.write(s);
translationMatters.write(s);
scaleMatters.write(s);
TSIntegerSet dummy;
dummy.write(s); // DEPRECIATED: Decals
dummy.write(s); // DEPRECIATED: Ifl materials
visMatters.write(s);
frameMatters.write(s);
matFrameMatters.write(s);
}
void TSShape::writeName(Stream * s, S32 nameIndex)
{
const char * name = "";
if (nameIndex>=0)
name = names[nameIndex];
S32 sz = (S32)dStrlen(name);
s->write(sz);
if (sz)
s->write(sz*sizeof(char),name);
}
S32 TSShape::readName(Stream * s, bool addName)
{
static char buffer[256];
S32 sz;
S32 nameIndex = -1;
s->read(&sz);
if (sz)
{
s->read(sz*sizeof(char),buffer);
buffer[sz] = '\0';
nameIndex = findName(buffer);
// Many modeling apps don't support spaces in names, so if the lookup
// failed, try the name again with spaces replaced by underscores
if (nameIndex < 0)
{
while (char *p = dStrchr(buffer, ' '))
*p = '_';
nameIndex = findName(buffer);
}
if (nameIndex<0 && addName)
{
nameIndex = names.size();
names.increment();
names.last() = buffer;
}
}
return nameIndex;
}

View file

@ -0,0 +1,168 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsSortedMesh.h"
#include "math/mMath.h"
#include "ts/tsShapeInstance.h"
// Not worth the effort, much less the effort to comment, but if the draw types
// are consecutive use addition rather than a table to go from index to command value...
/*
#if ((GL_TRIANGLES+1==GL_TRIANGLE_STRIP) && (GL_TRIANGLE_STRIP+1==GL_TRIANGLE_FAN))
#define getDrawType(a) (GL_TRIANGLES+(a))
#else
U32 drawTypes[] = { GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN };
#define getDrawType(a) (drawTypes[a])
#endif
*/
// found in tsmesh
extern void forceFaceCamera();
extern void forceFaceCameraZAxis();
//-----------------------------------------------------
// TSSortedMesh render methods
//-----------------------------------------------------
void TSSortedMesh::render(S32 frame, S32 matFrame, TSMaterialList * materials)
{
}
//-----------------------------------------------------
// TSSortedMesh collision methods
//-----------------------------------------------------
bool TSSortedMesh::buildPolyList( S32 frame, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials )
{
TORQUE_UNUSED(frame);
TORQUE_UNUSED(polyList);
TORQUE_UNUSED(surfaceKey);
TORQUE_UNUSED(materials);
return false;
}
bool TSSortedMesh::castRay( S32 frame, const Point3F &start, const Point3F &end, RayInfo *rayInfo, TSMaterialList *materials )
{
TORQUE_UNUSED(frame);
TORQUE_UNUSED(start);
TORQUE_UNUSED(end);
TORQUE_UNUSED(rayInfo);
TORQUE_UNUSED(materials);
return false;
}
bool TSSortedMesh::buildConvexHull()
{
return false;
}
S32 TSSortedMesh::getNumPolys()
{
S32 count = 0;
S32 cIdx = !clusters.size() ? -1 : 0;
while (cIdx>=0)
{
Cluster & cluster = clusters[cIdx];
for (S32 i=cluster.startPrimitive; i<cluster.endPrimitive; i++)
{
if (primitives[i].matIndex & TSDrawPrimitive::Triangles)
count += primitives[i].numElements / 3;
else
count += primitives[i].numElements - 2;
}
cIdx = cluster.frontCluster; // always use frontCluster...we assume about the same no matter what
}
return count;
}
//-----------------------------------------------------
// TSSortedMesh assembly/dissembly methods
// used for transfer to/from memory buffers
//-----------------------------------------------------
#define tsalloc TSShape::smTSAlloc
void TSSortedMesh::assemble(bool skip)
{
bool save1 = TSMesh::smUseTriangles;
bool save2 = TSMesh::smUseOneStrip;
TSMesh::smUseTriangles = false;
TSMesh::smUseOneStrip = false;
TSMesh::assemble(skip);
TSMesh::smUseTriangles = save1;
TSMesh::smUseOneStrip = save2;
S32 numClusters = tsalloc.get32();
S32 * ptr32 = tsalloc.copyToShape32(numClusters*8);
clusters.set(ptr32,numClusters);
S32 sz = tsalloc.get32();
ptr32 = tsalloc.copyToShape32(sz);
startCluster.set(ptr32,sz);
sz = tsalloc.get32();
ptr32 = tsalloc.copyToShape32(sz);
firstVerts.set(ptr32,sz);
sz = tsalloc.get32();
ptr32 = tsalloc.copyToShape32(sz);
numVerts.set(ptr32,sz);
sz = tsalloc.get32();
ptr32 = tsalloc.copyToShape32(sz);
firstTVerts.set(ptr32,sz);
alwaysWriteDepth = tsalloc.get32()!=0;
tsalloc.checkGuard();
}
void TSSortedMesh::disassemble()
{
TSMesh::disassemble();
tsalloc.set32(clusters.size());
tsalloc.copyToBuffer32((S32*)clusters.address(),clusters.size()*8);
tsalloc.set32(startCluster.size());
tsalloc.copyToBuffer32((S32*)startCluster.address(),startCluster.size());
tsalloc.set32(firstVerts.size());
tsalloc.copyToBuffer32((S32*)firstVerts.address(),firstVerts.size());
tsalloc.set32(numVerts.size());
tsalloc.copyToBuffer32((S32*)numVerts.address(),numVerts.size());
tsalloc.set32(firstTVerts.size());
tsalloc.copyToBuffer32((S32*)firstTVerts.address(),firstTVerts.size());
tsalloc.set32(alwaysWriteDepth ? 1 : 0);
tsalloc.setGuard();
}

View file

@ -0,0 +1,80 @@
//-----------------------------------------------------------------------------
// 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 _TSSORTEDMESH_H_
#define _TSSORTEDMESH_H_
#ifndef _TSMESH_H_
#include "ts/tsMesh.h"
#endif
/// TSSortedMesh is for meshes that need sorting (obviously). Such meshes
/// are usually partially or completely composed of translucent/parent polygons.
class TSSortedMesh : public TSMesh
{
public:
typedef TSMesh Parent;
/// This is a group of primitives that belong "together" in the rendering sequence.
/// For example, if a player model had a helmet with a translucent visor, the visor
/// would be a Cluster.
struct Cluster
{
S32 startPrimitive;
S32 endPrimitive;
Point3F normal;
F32 k;
S32 frontCluster; ///< go to this cluster if in front of plane, if frontCluster<0, no cluster
S32 backCluster; ///< go to this cluster if in back of plane, if backCluster<0, no cluster
///< if frontCluster==backCluster, no plane to test against...
};
Vector<Cluster> clusters; ///< All of the clusters of primitives to be drawn
Vector<S32> startCluster; ///< indexed by frame number
Vector<S32> firstVerts; ///< indexed by frame number
Vector<S32> numVerts; ///< indexed by frame number
Vector<S32> firstTVerts; ///< indexed by frame number or matFrame number, depending on which one animates (never both)
/// sometimes, we want to write the depth value to the frame buffer even when object is translucent
bool alwaysWriteDepth;
// render methods..
void render(S32 frame, S32 matFrame, TSMaterialList *);
bool buildPolyList( S32 frame, AbstractPolyList *polyList, U32 &surfaceKey, TSMaterialList *materials );
bool castRay( S32 frame, const Point3F &start, const Point3F &end, RayInfo *rayInfo, TSMaterialList *materials );
bool buildConvexHull(); ///< does nothing, skins don't use this
///
/// @returns false ALWAYS
S32 getNumPolys();
void assemble(bool skip);
void disassemble();
TSSortedMesh() {
meshType = SortedMeshType;
}
};
#endif // _TS_SORTED_MESH

View file

@ -0,0 +1,835 @@
//-----------------------------------------------------------------------------
// 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/tsShapeInstance.h"
//-------------------------------------------------------------------------------------
// This file contains the shape instance thread class (defined in tsShapeInstance.h)
// and the tsShapeInstance functions to interface with the thread class.
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
// Thread class
//-------------------------------------------------------------------------------------
// given a position on the thread, choose correct keyframes
// slight difference between one-shot and cyclic sequences -- see comments below for details
void TSThread::selectKeyframes(F32 pos, const TSSequence * seq, S32 * k1, S32 * k2, F32 * kpos)
{
S32 numKF = seq->numKeyframes;
F32 kf;
if (seq->isCyclic())
{
// cyclic sequence:
// pos=0 and pos=1 are equivalent, so we don't have a keyframe at pos=1
// last keyframe corresponds to pos=n/(n-1) up to (not including) pos=1
// (where n == num keyframes)
AssertFatal(pos>=0.0f && pos<1.0f,"TSThread::selectKeyframes");
kf = pos * (F32) (numKF);
// set keyPos
if (kpos)
*kpos = kf - (S32) kf;
// make sure compiler doing what we want...
AssertFatal(*kpos>=0.0f && *kpos<1.0f,"TSThread::selectKeyframes");
S32 kfIdx1 = (S32) kf;
// following assert could happen if pos1<1 && pos1==1...paradoxically...
AssertFatal(kfIdx1<=seq->numKeyframes,"TSThread::selectKeyframes");
S32 kfIdx2 = (kfIdx1==seq->numKeyframes-1) ? 0 : kfIdx1+1;
if (k1)
*k1 = kfIdx1;
if (k2)
*k2 = kfIdx2;
}
else
{
// one-shot sequence:
// pos=0 and pos=1 are now different, so we have a keyframe at pos=1
// last keyframe corresponds to pos=1
// rest of the keyframes are equally spaced (so 1/(n-1) pos units long)
// (where n == num keyframes)
AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::selectKeyframes");
if (pos==1.0f)
{
if (kpos)
*kpos = 0.0f;
if (k1)
*k1 = seq->numKeyframes-1;
if (k2)
*k2 = seq->numKeyframes-1;
}
else
{
kf = pos * (F32) (numKF-1);
// set keyPos
if (kpos)
*kpos = kf - (S32) kf;
S32 kfIdx1 = (S32) kf;
// following assert could happen if pos1<1 && pos1==1...paradoxically...
AssertFatal(kfIdx1<seq->numKeyframes,"TSThread::selectKeyFrames: invalid keyframe!");
S32 kfIdx2 = kfIdx1+1;
if (k1)
*k1 = kfIdx1;
if (k2)
*k2 = kfIdx2;
}
}
}
void TSThread::getGround(F32 t, MatrixF * pMat)
{
static const QuatF unitRotation(0,0,0,1);
static const Point3F unitTranslation = Point3F::Zero;
const QuatF * q1, * q2;
QuatF rot1,rot2;
const Point3F * p1, * p2;
// if N = sequence->numGroundFrames, then there are N+1 positions we
// interpolate betweeen: 0/N, 1/N ... N/N
// we need to convert the p passed to us into 2 ground keyframes:
// the 0.99999f is in case 'p' is exactly 1.0f, which is legal but 'kf'
// needs to be strictly less than 'sequence->numGroundFrames'
F32 kf = 0.999999f * t * (F32) getSequence()->numGroundFrames;
// get frame number and interp param (kpos)
S32 frame = (S32)kf;
F32 kpos = kf - (F32)frame;
// now point pT1 and pT2 at transforms for keyframes 'frame' and 'frame+1'
// following a little strange: first ground keyframe (0/N in comment above) is
// assumed to be ident. and not found in the list.
if (frame)
{
p1 = &mShapeInstance->mShape->groundTranslations[getSequence()->firstGroundFrame + frame - 1];
q1 = &mShapeInstance->mShape->groundRotations[getSequence()->firstGroundFrame + frame - 1].getQuatF(&rot1);
}
else
{
p1 = &unitTranslation;
q1 = &unitRotation;
}
// similar to above, ground keyframe number 'frame+1' is actually offset by 'frame'
p2 = &mShapeInstance->mShape->groundTranslations[getSequence()->firstGroundFrame + frame];
q2 = &mShapeInstance->mShape->groundRotations[getSequence()->firstGroundFrame + frame].getQuatF(&rot2);
QuatF q;
Point3F p;
TSTransform::interpolate(*q1,*q2,kpos,&q);
TSTransform::interpolate(*p1,*p2,kpos,&p);
TSTransform::setMatrix(q,p,pMat);
}
void TSThread::setSequence(S32 seq, F32 toPos)
{
const TSShape * shape = mShapeInstance->mShape;
AssertFatal(shape && shape->sequences.size()>seq && toPos>=0.0f && toPos<=1.0f,
"TSThread::setSequence: invalid shape handle, sequence number, or position.");
mShapeInstance->clearTransition(this);
sequence = seq;
priority = getSequence()->priority;
pos = toPos;
makePath = getSequence()->makePath();
path.start = path.end = 0;
path.loop = 0;
// 1.0f doesn't exist on cyclic sequences
if (pos>0.9999f && getSequence()->isCyclic())
pos = 0.9999f;
// select keyframes
selectKeyframes(pos,getSequence(),&keyNum1,&keyNum2,&keyPos);
}
void TSThread::transitionToSequence(S32 seq, F32 toPos, F32 duration, bool continuePlay)
{
AssertFatal(duration>=0.0f,"TSThread::transitionToSequence: negative duration not allowed");
// make sure these nodes are smoothly interpolated to new positions...
// basically, any node we controlled just prior to transition, or at any stage
// of the transition is interpolated. If we start to transtion from A to B,
// but before reaching B we transtion to C, we interpolate all nodes controlled
// by A, B, or C to their new position.
if (transitionData.inTransition)
{
transitionData.oldRotationNodes.overlap(getSequence()->rotationMatters);
transitionData.oldTranslationNodes.overlap(getSequence()->translationMatters);
transitionData.oldScaleNodes.overlap(getSequence()->scaleMatters);
}
else
{
transitionData.oldRotationNodes = getSequence()->rotationMatters;
transitionData.oldTranslationNodes = getSequence()->translationMatters;
transitionData.oldScaleNodes = getSequence()->scaleMatters;
}
// set time characteristics of transition
transitionData.oldSequence = sequence;
transitionData.oldPos = pos;
transitionData.duration = duration;
transitionData.pos = 0.0f;
transitionData.direction = timeScale>0.0f ? 1.0f : -1.0f;
transitionData.targetScale = continuePlay ? 1.0f : 0.0f;
// in transition...
transitionData.inTransition = true;
// set target sequence data
sequence = seq;
priority = getSequence()->priority;
pos = toPos;
makePath = getSequence()->makePath();
path.start = path.end = 0;
path.loop = 0;
// 1.0f doesn't exist on cyclic sequences
if (pos>0.9999f && getSequence()->isCyclic())
pos = 0.9999f;
// select keyframes
selectKeyframes(pos,getSequence(),&keyNum1,&keyNum2,&keyPos);
}
bool TSThread::isInTransition()
{
return transitionData.inTransition;
}
void TSThread::animateTriggers()
{
if (!getSequence()->numTriggers)
return;
switch (path.loop)
{
case -1 :
activateTriggers(path.start,0);
activateTriggers(1,path.end);
break;
case 0 :
activateTriggers(path.start,path.end);
break;
case 1 :
activateTriggers(path.start,1);
activateTriggers(0,path.end);
break;
default:
{
if (path.loop>0)
{
activateTriggers(path.end,1);
activateTriggers(0,path.end);
}
else
{
activateTriggers(path.end,0);
activateTriggers(1,path.end);
}
}
}
}
void TSThread::activateTriggers(F32 a, F32 b)
{
S32 i;
const TSShape * shape = mShapeInstance->mShape;
S32 firstTrigger = getSequence()->firstTrigger;
S32 numTriggers = getSequence()->numTriggers;
// first find triggers at position a and b
// we assume there aren't many triggers, so
// search is linear
F32 lastPos = -1.0f;
S32 aIndex = numTriggers+firstTrigger; // initialized to handle case where pos past all triggers
S32 bIndex = numTriggers+firstTrigger; // initialized to handle case where pos past all triggers
for (i=firstTrigger; i<numTriggers+firstTrigger; i++)
{
// is a between this trigger and previous one...
if (a>lastPos && a<=shape->triggers[i].pos)
aIndex = i;
// is b between this trigger and previous one...
if (b>lastPos && b<=shape->triggers[i].pos)
bIndex = i;
lastPos = shape->triggers[i].pos;
}
// activate triggers between aIndex and bIndex (depends on direction)
if (aIndex<=bIndex)
{
for (i=aIndex; i<bIndex; i++)
{
U32 state = shape->triggers[i].state;
bool on = (state & TSShape::Trigger::StateOn)!=0;
mShapeInstance->setTriggerStateBit(state & TSShape::Trigger::StateMask, on);
}
}
else
{
for (i=aIndex-1; i>=bIndex; i--)
{
U32 state = shape->triggers[i].state;
bool on = (state & TSShape::Trigger::StateOn)!=0;
if (state & TSShape::Trigger::InvertOnReverse)
on = !on;
mShapeInstance->setTriggerStateBit(state & TSShape::Trigger::StateMask, on);
}
}
}
F32 TSThread::getPos()
{
return transitionData.inTransition ? transitionData.pos : pos;
}
F32 TSThread::getTime()
{
return transitionData.inTransition ? transitionData.pos * transitionData.duration : pos * getSequence()->duration;
}
F32 TSThread::getDuration()
{
return transitionData.inTransition ? transitionData.duration : getSequence()->duration;
}
F32 TSThread::getScaledDuration()
{
return getDuration() / mFabs(timeScale);
}
F32 TSThread::getTimeScale()
{
return timeScale;
}
void TSThread::setTimeScale(F32 ts)
{
timeScale = ts;
}
void TSThread::advancePos(F32 delta)
{
if (mFabs(delta)>0.00001f)
{
// make dirty what this thread changes
U32 dirtyFlags = getSequence()->dirtyFlags | (transitionData.inTransition ? TSShapeInstance::TransformDirty : 0);
for (S32 i=0; i<mShapeInstance->getShape()->subShapeFirstNode.size(); i++)
mShapeInstance->mDirtyFlags[i] |= dirtyFlags;
}
if (transitionData.inTransition)
{
transitionData.pos += transitionData.direction * delta;
if (transitionData.pos<0 || transitionData.pos>=1.0f)
{
mShapeInstance->clearTransition(this);
if (transitionData.pos<0.0f)
// return to old sequence
mShapeInstance->setSequence(this,transitionData.oldSequence,transitionData.oldPos);
}
// re-adjust delta to be correct time-wise
delta *= transitionData.targetScale * transitionData.duration / getSequence()->duration;
}
// even if we are in a transition, keep playing the sequence
if (makePath)
{
path.start = pos;
pos += delta;
if (!getSequence()->isCyclic())
{
pos = mClampF(pos , 0.0f, 1.0f);
path.loop = 0;
}
else
{
path.loop = (S32)pos;
if (pos < 0.0f)
path.loop--;
pos -= path.loop;
// following necessary because of floating point roundoff errors
if (pos < 0.0f) pos += 1.0f;
if (pos >= 1.0f) pos -= 1.0f;
}
path.end = pos;
animateTriggers(); // do this automatically...no need for user to call it
AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::advancePos (1)");
AssertFatal(!getSequence()->isCyclic() || pos<1.0f,"TSThread::advancePos (2)");
}
else
{
pos += delta;
if (!getSequence()->isCyclic())
pos = mClampF(pos, 0.0f, 1.0f);
else
{
pos -= S32(pos);
// following necessary because of floating point roundoff errors
if (pos < 0.0f) pos += 1.0f;
if (pos >= 1.0f) pos -= 1.0f;
}
AssertFatal(pos>=0.0f && pos<=1.0f,"TSThread::advancePos (3)");
AssertFatal(!getSequence()->isCyclic() || pos<1.0f,"TSThread::advancePos (4)");
}
// select keyframes
selectKeyframes(pos,getSequence(),&keyNum1,&keyNum2,&keyPos);
}
void TSThread::advanceTime(F32 delta)
{
advancePos(timeScale * delta / getDuration());
}
void TSThread::setPos(F32 pos)
{
advancePos(pos-getPos());
}
void TSThread::setTime(F32 time)
{
setPos(timeScale * time/getDuration());
}
S32 TSThread::getKeyframeCount()
{
AssertFatal(!transitionData.inTransition,"TSThread::getKeyframeCount: not while in transition");
return getSequence()->numKeyframes + 1;
}
S32 TSThread::getKeyframeNumber()
{
AssertFatal(!transitionData.inTransition,"TSThread::getKeyframeNumber: not while in transition");
return keyNum1;
}
void TSThread::setKeyframeNumber(S32 kf)
{
AssertFatal(kf>=0 && kf<= getSequence()->numKeyframes,
"TSThread::setKeyframeNumber: invalid frame specified.");
AssertFatal(!transitionData.inTransition,"TSThread::setKeyframeNumber: not while in transition");
keyNum1 = keyNum2 = kf;
keyPos = 0;
pos = 0;
}
TSThread::TSThread(TSShapeInstance * _shapeInst)
{
timeScale = 1.0f;
mShapeInstance = _shapeInst;
transitionData.inTransition = false;
blendDisabled = false;
setSequence(0,0.0f);
}
S32 TSThread::operator<(const TSThread & th2) const
{
if (getSequence()->isBlend() == th2.getSequence()->isBlend())
{
// both blend or neither blend, sort based on priority only -- higher priority first
S32 ret = 0; // do it this way to (hopefully) take advantage of 'conditional move' assembly instruction
if (priority > th2.priority)
ret = -1;
if (th2.priority > priority)
ret = 1;
return ret;
}
else
{
// one is blend, the other is not...sort based on blend -- non-blended first
AssertFatal(!getSequence()->isBlend() || !th2.getSequence()->isBlend(),"compareThreads: unequal 'trues'");
S32 ret = -1; // do it this way to (hopefully) take advantage of 'conditional move' assembly instruction
if (getSequence()->isBlend())
ret = 1;
return ret;
}
}
//-------------------------------------------------------------------------------------
// TSShapeInstance Thread Interface -- more implemented in header file
//-------------------------------------------------------------------------------------
TSThread * TSShapeInstance::addThread()
{
if (mShape->sequences.empty())
return NULL;
mThreadList.increment();
mThreadList.last() = new TSThread(this);
setDirty(AllDirtyMask);
return mThreadList.last();
}
TSThread * TSShapeInstance::getThread(S32 threadNumber)
{
AssertFatal(threadNumber < mThreadList.size() && threadNumber>=0,"TSShapeInstance::getThread: threadNumber out of bounds.");
return mThreadList[threadNumber];
}
void TSShapeInstance::destroyThread(TSThread * thread)
{
if (!thread)
return;
clearTransition(thread);
S32 i;
for (i=0; i<mThreadList.size(); i++)
if (thread==mThreadList[i])
break;
AssertFatal(i<mThreadList.size(),"TSShapeInstance::destroyThread was requested to destroy a thread that this instance doesn't own!");
delete mThreadList[i];
mThreadList.erase(i);
setDirty(AllDirtyMask);
checkScaleCurrentlyAnimated();
}
U32 TSShapeInstance::threadCount()
{
return mThreadList.size();
}
void TSShapeInstance::setSequence(TSThread * thread, S32 seq, F32 pos)
{
if ( (thread->transitionData.inTransition && mTransitionThreads.size()>1) || mTransitionThreads.size()>0)
{
// if we have transitions, make sure transforms are up to date...
sortThreads();
animateNodeSubtrees();
}
thread->setSequence(seq,pos);
setDirty(AllDirtyMask);
mGroundThread = NULL;
if (mScaleCurrentlyAnimated && !thread->getSequence()->animatesScale())
checkScaleCurrentlyAnimated();
else if (!mScaleCurrentlyAnimated && thread->getSequence()->animatesScale())
mScaleCurrentlyAnimated=true;
updateTransitions();
}
U32 TSShapeInstance::getSequence(TSThread * thread)
{
//AssertFatal( thread->sequence >= 0, "TSShapeInstance::getSequence: range error A");
//AssertFatal( thread->sequence < mShape->sequences.size(), "TSShapeInstance::getSequence: range error B");
return (U32)thread->sequence;
}
void TSShapeInstance::transitionToSequence(TSThread * thread, S32 seq, F32 pos, F32 duration, bool continuePlay)
{
// make sure all transforms on all detail levels are accurate
sortThreads();
animateNodeSubtrees();
thread->transitionToSequence(seq,pos,duration,continuePlay);
setDirty(AllDirtyMask);
mGroundThread = NULL;
if (mScaleCurrentlyAnimated && !thread->getSequence()->animatesScale())
checkScaleCurrentlyAnimated();
else if (!mScaleCurrentlyAnimated && thread->getSequence()->animatesScale())
mScaleCurrentlyAnimated=true;
mTransitionRotationNodes.overlap(thread->transitionData.oldRotationNodes);
mTransitionRotationNodes.overlap(thread->getSequence()->rotationMatters);
mTransitionTranslationNodes.overlap(thread->transitionData.oldTranslationNodes);
mTransitionTranslationNodes.overlap(thread->getSequence()->translationMatters);
mTransitionScaleNodes.overlap(thread->transitionData.oldScaleNodes);
mTransitionScaleNodes.overlap(thread->getSequence()->scaleMatters);
// if we aren't already in the list of transition threads, add us now
S32 i;
for (i=0; i<mTransitionThreads.size(); i++)
if (mTransitionThreads[i]==thread)
break;
if (i==mTransitionThreads.size())
mTransitionThreads.push_back(thread);
updateTransitions();
}
void TSShapeInstance::clearTransition(TSThread * thread)
{
if (!thread->transitionData.inTransition)
return;
// if other transitions are still playing,
// make sure transforms are up to date
if (mTransitionThreads.size()>1)
animateNodeSubtrees();
// turn off transition...
thread->transitionData.inTransition = false;
// remove us from transition list
S32 i;
if (mTransitionThreads.size() != 0) {
for (i=0; i<mTransitionThreads.size(); i++)
if (mTransitionThreads[i]==thread)
break;
AssertFatal(i!=mTransitionThreads.size(),"TSShapeInstance::clearTransition");
mTransitionThreads.erase(i);
}
// recompute transitionNodes
mTransitionRotationNodes.clearAll();
mTransitionTranslationNodes.clearAll();
mTransitionScaleNodes.clearAll();
for (i=0; i<mTransitionThreads.size(); i++)
{
mTransitionRotationNodes.overlap(mTransitionThreads[i]->transitionData.oldRotationNodes);
mTransitionRotationNodes.overlap(mTransitionThreads[i]->getSequence()->rotationMatters);
mTransitionTranslationNodes.overlap(mTransitionThreads[i]->transitionData.oldTranslationNodes);
mTransitionTranslationNodes.overlap(mTransitionThreads[i]->getSequence()->translationMatters);
mTransitionScaleNodes.overlap(mTransitionThreads[i]->transitionData.oldScaleNodes);
mTransitionScaleNodes.overlap(mTransitionThreads[i]->getSequence()->scaleMatters);
}
setDirty(ThreadDirty);
updateTransitions();
}
void TSShapeInstance::updateTransitions()
{
if (mTransitionThreads.empty())
return;
TSIntegerSet transitionNodes;
updateTransitionNodeTransforms(transitionNodes);
S32 i;
mNodeReferenceRotations.setSize(mShape->nodes.size());
mNodeReferenceTranslations.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionRotationNodes.test(i))
mNodeReferenceRotations[i].set(smNodeCurrentRotations[i]);
if (mTransitionTranslationNodes.test(i))
mNodeReferenceTranslations[i] = smNodeCurrentTranslations[i];
}
if (animatesScale())
{
// Make sure smNodeXXXScale arrays have been resized
TSIntegerSet dummySet;
handleDefaultScale(0, 0, dummySet);
if (animatesUniformScale())
{
mNodeReferenceUniformScales.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionScaleNodes.test(i))
mNodeReferenceUniformScales[i] = smNodeCurrentUniformScales[i];
}
}
else if (animatesAlignedScale())
{
mNodeReferenceScaleFactors.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionScaleNodes.test(i))
mNodeReferenceScaleFactors[i] = smNodeCurrentAlignedScales[i];
}
}
else
{
mNodeReferenceScaleFactors.setSize(mShape->nodes.size());
mNodeReferenceArbitraryScaleRots.setSize(mShape->nodes.size());
for (i=0; i<mShape->nodes.size(); i++)
{
if (mTransitionScaleNodes.test(i))
{
mNodeReferenceScaleFactors[i] = smNodeCurrentArbitraryScales[i].mScale;
mNodeReferenceArbitraryScaleRots[i].set(smNodeCurrentArbitraryScales[i].mRotate);
}
}
}
}
// reset transition durations to account for new reference transforms
for (i=0; i<mTransitionThreads.size(); i++)
{
TSThread * th = mTransitionThreads[i];
if (th->transitionData.inTransition)
{
th->transitionData.duration *= 1.0f - th->transitionData.pos;
th->transitionData.pos = 0.0f;
}
}
}
void TSShapeInstance::checkScaleCurrentlyAnimated()
{
mScaleCurrentlyAnimated=true;
for (S32 i=0; i<mThreadList.size(); i++)
if (mThreadList[i]->getSequence()->animatesScale())
return;
mScaleCurrentlyAnimated=false;
}
void TSShapeInstance::setBlendEnabled(TSThread * thread, bool blendOn)
{
thread->blendDisabled = !blendOn;
}
bool TSShapeInstance::getBlendEnabled(TSThread * thread)
{
return !thread->blendDisabled;
}
void TSShapeInstance::setPriority(TSThread * thread, F32 priority)
{
thread->priority = priority;
}
F32 TSShapeInstance::getPriority(TSThread * thread)
{
return thread->priority;
}
F32 TSShapeInstance::getTime(TSThread * thread)
{
return thread->getTime();
}
F32 TSShapeInstance::getPos(TSThread * thread)
{
return thread->getPos();
}
void TSShapeInstance::setTime(TSThread * thread, F32 time)
{
thread->setTime(time);
}
void TSShapeInstance::setPos(TSThread * thread, F32 pos)
{
thread->setPos(pos);
}
bool TSShapeInstance::isInTransition(TSThread * thread)
{
return thread->isInTransition();
}
F32 TSShapeInstance::getTimeScale(TSThread * thread)
{
return thread->getTimeScale();
}
void TSShapeInstance::setTimeScale(TSThread * thread, F32 timeScale)
{
thread->setTimeScale(timeScale);
}
F32 TSShapeInstance::getDuration(TSThread * thread)
{
return thread->getDuration();
}
F32 TSShapeInstance::getScaledDuration(TSThread * thread)
{
return thread->getScaledDuration();
}
S32 TSShapeInstance::getKeyframeCount(TSThread * thread)
{
return thread->getKeyframeCount();
}
S32 TSShapeInstance::getKeyframeNumber(TSThread * thread)
{
return thread->getKeyframeNumber();
}
void TSShapeInstance::setKeyframeNumber(TSThread * thread, S32 kf)
{
thread->setKeyframeNumber(kf);
}
// advance time on a particular thread
void TSShapeInstance::advanceTime(F32 delta, TSThread * thread)
{
thread->advanceTime(delta);
}
// advance time on all threads
void TSShapeInstance::advanceTime(F32 delta)
{
for (S32 i=0; i<mThreadList.size(); i++)
mThreadList[i]->advanceTime(delta);
}
// advance pos on a particular thread
void TSShapeInstance::advancePos(F32 delta, TSThread * thread)
{
thread->advancePos(delta);
}
// advance pos on all threads
void TSShapeInstance::advancePos(F32 delta)
{
for (S32 i=0; i<mThreadList.size(); i++)
mThreadList[i]->advancePos(delta);
}

View file

@ -0,0 +1,156 @@
//-----------------------------------------------------------------------------
// 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 "ts/tsTransform.h"
#include "core/stream/stream.h"
void Quat16::identity()
{
x = y = z = 0;
w = MAX_VAL;
}
QuatF Quat16::getQuatF() const
{
return QuatF ( F32(x) / F32(MAX_VAL),
F32(y) / F32(MAX_VAL),
F32(z) / F32(MAX_VAL),
F32(w) / F32(MAX_VAL) );
}
QuatF & Quat16::getQuatF( QuatF * q ) const
{
q->x = F32( x ) / F32(MAX_VAL);
q->y = F32( y ) / F32(MAX_VAL);
q->z = F32( z ) / F32(MAX_VAL);
q->w = F32( w ) / F32(MAX_VAL);
return *q;
}
void Quat16::set( const QuatF & q )
{
x = (S16)(q.x * F32(MAX_VAL));
y = (S16)(q.y * F32(MAX_VAL));
z = (S16)(q.z * F32(MAX_VAL));
w = (S16)(q.w * F32(MAX_VAL));
}
S32 Quat16::operator==( const Quat16 & q ) const
{
return( x == q.x && y == q.y && z == q.z && w == q.w );
}
void Quat16::read(Stream * s)
{
s->read(&x);
s->read(&y);
s->read(&z);
s->read(&w);
}
void Quat16::write(Stream * s)
{
s->write(x);
s->write(y);
s->write(z);
s->write(w);
}
QuatF & TSTransform::interpolate( const QuatF & q1, const QuatF & q2, F32 interp, QuatF * q )
{
F32 Dot;
F32 Dist2;
F32 OneOverL;
F32 x1,y1,z1,w1;
F32 x2,y2,z2,w2;
//
// This is a linear interpolation with a fast renormalization.
//
x1 = q1.x;
y1 = q1.y;
z1 = q1.z;
w1 = q1.w;
x2 = q2.x;
y2 = q2.y;
z2 = q2.z;
w2 = q2.w;
// Determine if quats are further than 90 degrees
Dot = x1*x2 + y1*y2 + z1*z2 + w1*w2;
// If dot is negative flip one of the quaterions
if( Dot < 0.0f )
{
x1 = -x1;
y1 = -y1;
z1 = -z1;
w1 = -w1;
}
// Compute interpolated values
x1 = x1 + interp*(x2 - x1);
y1 = y1 + interp*(y2 - y1);
z1 = z1 + interp*(z2 - z1);
w1 = w1 + interp*(w2 - w1);
// Get squared distance of new quaternion
Dist2 = x1*x1 + y1*y1 + z1*z1 + w1*w1;
// Use home-baked polynomial to compute 1/sqrt(Dist2)
// since we know the range is 0.707 >= Dist2 <= 1.0
// we'll split in half.
if( Dist2<0.857f )
OneOverL = (((0.699368f)*Dist2) + -1.819985f)*Dist2 + 2.126369f; //0.0000792
else
OneOverL = (((0.454012f)*Dist2) + -1.403517f)*Dist2 + 1.949542f; //0.0000373
// Renormalize
q->x = x1*OneOverL;
q->y = y1*OneOverL;
q->z = z1*OneOverL;
q->w = w1*OneOverL;
return *q;
}
void TSTransform::applyScale(F32 scale, MatrixF * mat)
{
mat->scale(Point3F(scale,scale,scale));
}
void TSTransform::applyScale(const Point3F & scale, MatrixF * mat)
{
mat->scale(scale);
}
void TSTransform::applyScale(const TSScale & scale, MatrixF * mat)
{
MatrixF mat2;
TSTransform::setMatrix(scale.mRotate,&mat2);
MatrixF mat3(mat2);
mat3.inverse();
mat2.scale(scale.mScale);
mat2.mul(mat3);
mat->mul(mat2);
}

View file

@ -0,0 +1,109 @@
//-----------------------------------------------------------------------------
// 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 _TSTRANSFORM_H_
#define _TSTRANSFORM_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
class Stream;
/// compressed quaternion class
struct Quat16
{
enum { MAX_VAL = 0x7fff };
S16 x, y, z, w;
void read(Stream *);
void write(Stream *);
void identity();
QuatF getQuatF() const;
QuatF &getQuatF( QuatF * q ) const;
void set( const QuatF & q );
S32 operator==( const Quat16 & q ) const;
S32 operator!=( const Quat16 & q ) const { return !(*this == q); }
};
/// class to handle general scaling case
///
/// transform = rot * scale * inv(rot)
struct TSScale
{
QuatF mRotate;
Point3F mScale;
void identity() { mRotate.identity(); mScale.set( 1.0f,1.0f,1.0f ); }
S32 operator==( const TSScale & other ) const { return mRotate==other.mRotate && mScale==other.mScale; }
};
/// struct for encapsulating ts transform related static functions
struct TSTransform
{
static Point3F & interpolate(const Point3F & p1, const Point3F & p2, F32 t, Point3F *);
static F32 interpolate(F32 p1, F32 p2, F32 t);
static QuatF & interpolate(const QuatF & q1, const QuatF & q2, F32 t, QuatF *);
static TSScale & interpolate(const TSScale & s1, const TSScale & s2, F32 t, TSScale *);
static void setMatrix(const QuatF & q, MatrixF *);
static void setMatrix(const QuatF & q, const Point3F & p, MatrixF *);
static void applyScale(F32 scale, MatrixF *);
static void applyScale(const Point3F & scale, MatrixF *);
static void applyScale(const TSScale & scale, MatrixF *);
};
inline Point3F & TSTransform::interpolate(const Point3F & p1, const Point3F & p2, F32 t, Point3F * p)
{
p->x = p1.x + t * (p2.x-p1.x);
p->y = p1.y + t * (p2.y-p1.y);
p->z = p1.z + t * (p2.z-p1.z);
return *p;
}
inline F32 TSTransform::interpolate(F32 p1, F32 p2, F32 t)
{
return p1 + t*(p2-p1);
}
inline TSScale & TSTransform::interpolate(const TSScale & s1, const TSScale & s2, F32 t, TSScale * s)
{
TSTransform::interpolate(s1.mRotate,s2.mRotate,t,&s->mRotate);
TSTransform::interpolate(s1.mScale,s2.mScale,t,&s->mScale);
return *s;
}
inline void TSTransform::setMatrix( const QuatF & q, const Point3F & p, MatrixF * pDest )
{
q.setMatrix(pDest);
pDest->setColumn(3,p);
}
inline void TSTransform::setMatrix( const QuatF & q, MatrixF * pDest )
{
q.setMatrix(pDest);
}
#endif