mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-05-31 10:06:36 +00:00
final setup for assimp Added assimp importers and exporters, removed defaulting to all Added compression to tshape added dts version to tshape and cmake Update assimpShapeLoader.cpp quick fix fix previewing dsq ground work automatically export dsq files for animations Groundwork Adds the same sort of model for registering loaders and exporters as is set out on gbitmap Added a bit more safety around the assimp matrix fix to convert incoming models to torques coordinate system.
2570 lines
78 KiB
C++
2570 lines
78 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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/tsShape.h"
|
|
|
|
#include "ts/tsLastDetail.h"
|
|
#include "ts/tsMaterialList.h"
|
|
#include "core/stringTable.h"
|
|
#include "console/console.h"
|
|
#include "ts/tsShapeInstance.h"
|
|
#include "collision/convex.h"
|
|
#include "console/consoleInternal.h"
|
|
#include "console/script.h"
|
|
#include "materials/matInstance.h"
|
|
#include "materials/materialManager.h"
|
|
#include "math/mathIO.h"
|
|
#include "core/util/endian.h"
|
|
#include "core/stream/fileStream.h"
|
|
#include "core/fileObject.h"
|
|
|
|
Vector<TSShape::ShapeRegistration> TSShape::sRegistrations(__FILE__, __LINE__);
|
|
|
|
void TSShape::sRegisterFormat(const ShapeRegistration& reg)
|
|
{
|
|
U32 insert = sRegistrations.size();
|
|
sRegistrations.insert(insert, reg);
|
|
}
|
|
|
|
const TSShape::ShapeRegistration* TSShape::sFindRegInfo(const String& extension, bool exporting)
|
|
{
|
|
for (U32 i = 0; i < TSShape::sRegistrations.size(); i++)
|
|
{
|
|
const TSShape::ShapeRegistration& reg = TSShape::sRegistrations[i];
|
|
const Vector<ShapeFormat>& extensions = exporting ? reg.export_extensions : reg.extensions;
|
|
|
|
for (U32 j = 0; j < extensions.size(); j++)
|
|
{
|
|
if (extensions[j].mExtension.equal(extension, String::NoCase))
|
|
return ®
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/// most recent version -- this is the version we write
|
|
S32 TSShape::smVersion = TORQUE_DTS_VERSION;
|
|
/// the version currently being read...valid only during a read
|
|
S32 TSShape::smReadVersion = -1;
|
|
const U32 TSShape::smMostRecentExporterVersion = DTS_EXPORTER_CURRENT_VERSION;
|
|
|
|
F32 TSShape::smAlphaOutLastDetail = -1.0f;
|
|
F32 TSShape::smAlphaInBillboard = 0.15f;
|
|
F32 TSShape::smAlphaOutBillboard = 0.15f;
|
|
F32 TSShape::smAlphaInDefault = -1.0f;
|
|
F32 TSShape::smAlphaOutDefault = -1.0f;
|
|
|
|
// don't bother even loading this many of the highest detail levels (but
|
|
// always load last renderable detail)
|
|
S32 TSShape::smNumSkipLoadDetails = 0;
|
|
|
|
bool TSShape::smInitOnRead = true;
|
|
bool TSShape::smUseHardwareSkinning = true;
|
|
U32 TSShape::smMaxSkinBones = 70;
|
|
|
|
|
|
TSShape::TSShape()
|
|
{
|
|
mExporterVersion = 124;
|
|
mSmallestVisibleSize = 2;
|
|
mSmallestVisibleDL = 0;
|
|
mRadius = 0;
|
|
mFlags = 0;
|
|
tubeRadius = 0;
|
|
data = 0;
|
|
materialList = NULL;
|
|
mReadVersion = -1; // -1 means constructed from scratch (e.g., in exporter or no read yet)
|
|
mSequencesConstructed = false;
|
|
mShapeData = NULL;
|
|
mShapeDataSize = 0;
|
|
mVertexSize = 0;
|
|
mUseDetailFromScreenError = false;
|
|
mNeedReinit = false;
|
|
|
|
mDetailLevelLookup.setSize( 1 );
|
|
mDetailLevelLookup[0].set( -1, 0 );
|
|
|
|
VECTOR_SET_ASSOCIATION(sequences);
|
|
VECTOR_SET_ASSOCIATION(nodeRotations);
|
|
VECTOR_SET_ASSOCIATION(nodeTranslations);
|
|
VECTOR_SET_ASSOCIATION(nodeUniformScales);
|
|
VECTOR_SET_ASSOCIATION(nodeAlignedScales);
|
|
VECTOR_SET_ASSOCIATION(nodeArbitraryScaleRots);
|
|
VECTOR_SET_ASSOCIATION(nodeArbitraryScaleFactors);
|
|
VECTOR_SET_ASSOCIATION(groundRotations);
|
|
VECTOR_SET_ASSOCIATION(groundTranslations);
|
|
VECTOR_SET_ASSOCIATION(triggers);
|
|
VECTOR_SET_ASSOCIATION(billboardDetails);
|
|
VECTOR_SET_ASSOCIATION(detailCollisionAccelerators);
|
|
VECTOR_SET_ASSOCIATION(names);
|
|
|
|
VECTOR_SET_ASSOCIATION( nodes );
|
|
VECTOR_SET_ASSOCIATION( objects );
|
|
VECTOR_SET_ASSOCIATION( objectStates );
|
|
VECTOR_SET_ASSOCIATION( subShapeFirstNode );
|
|
VECTOR_SET_ASSOCIATION( subShapeFirstObject );
|
|
VECTOR_SET_ASSOCIATION( detailFirstSkin );
|
|
VECTOR_SET_ASSOCIATION( subShapeNumNodes );
|
|
VECTOR_SET_ASSOCIATION( subShapeNumObjects );
|
|
VECTOR_SET_ASSOCIATION( details );
|
|
VECTOR_SET_ASSOCIATION( defaultRotations );
|
|
VECTOR_SET_ASSOCIATION( defaultTranslations );
|
|
|
|
VECTOR_SET_ASSOCIATION( subShapeFirstTranslucentObject );
|
|
VECTOR_SET_ASSOCIATION( meshes );
|
|
|
|
VECTOR_SET_ASSOCIATION( alphaIn );
|
|
VECTOR_SET_ASSOCIATION( alphaOut );
|
|
}
|
|
|
|
TSShape::~TSShape()
|
|
{
|
|
delete materialList;
|
|
|
|
S32 i;
|
|
|
|
// everything left over here is a legit mesh
|
|
for (i=0; i<meshes.size(); i++)
|
|
{
|
|
if (!meshes[i])
|
|
continue;
|
|
|
|
// Handle meshes that were either assembled with the shape or added later
|
|
if (((S8*)meshes[i] >= mShapeData) && ((S8*)meshes[i] < (mShapeData + mShapeDataSize)))
|
|
destructInPlace(meshes[i]);
|
|
else
|
|
delete meshes[i];
|
|
}
|
|
|
|
for (i=0; i<billboardDetails.size(); i++)
|
|
{
|
|
delete billboardDetails[i];
|
|
billboardDetails[i] = NULL;
|
|
}
|
|
billboardDetails.clear();
|
|
|
|
// Delete any generated accelerators
|
|
S32 dca;
|
|
for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
|
|
{
|
|
ConvexHullAccelerator* accel = detailCollisionAccelerators[dca];
|
|
if (accel != NULL) {
|
|
delete [] accel->vertexList;
|
|
delete [] accel->normalList;
|
|
for (S32 j = 0; j < accel->numVerts; j++)
|
|
delete [] accel->emitStrings[j];
|
|
delete [] accel->emitStrings;
|
|
delete accel;
|
|
}
|
|
}
|
|
for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
|
|
detailCollisionAccelerators[dca] = NULL;
|
|
|
|
if( mShapeData )
|
|
delete[] mShapeData;
|
|
}
|
|
|
|
void TSShape::compressionKey(U8* keyOut, U32 keyLen)
|
|
{
|
|
// Concatenate app name and zip password at compile time
|
|
static const char kSeed[] = TORQUE_APP_NAME DEFAULT_ZIP_PASSWORD;
|
|
|
|
// djb2 hash to collapse the seed string into a 32-bit value
|
|
U32 state = 5381u;
|
|
for (const char* c = kSeed; *c; ++c)
|
|
state = ((state << 5) + state) ^ (U8)*c;
|
|
|
|
// Expand into a key stream via a linear-congruential generator
|
|
// (Numerical Recipes coefficients)
|
|
for (U32 i = 0; i < keyLen; ++i)
|
|
{
|
|
state = state * 1664525u + 1013904223u;
|
|
keyOut[i] = (U8)(state >> 16);
|
|
}
|
|
}
|
|
|
|
void TSShape::xorBufferAtOffset(void* data, U32 byteCount,
|
|
U32 startOffset,
|
|
const U8* key, U32 keyLen)
|
|
{
|
|
U8* p = (U8*)data;
|
|
for (U32 i = 0; i < byteCount; ++i)
|
|
p[i] ^= key[(startOffset + i) % keyLen];
|
|
}
|
|
|
|
const String& TSShape::getName( S32 nameIndex ) const
|
|
{
|
|
AssertFatal(nameIndex>=0 && nameIndex<names.size(),"TSShape::getName");
|
|
return names[nameIndex];
|
|
}
|
|
|
|
const String& TSShape::getMeshName( S32 meshIndex ) const
|
|
{
|
|
S32 nameIndex = objects[meshIndex].nameIndex;
|
|
if ( nameIndex < 0 )
|
|
return String::EmptyString;
|
|
|
|
return names[nameIndex];
|
|
}
|
|
|
|
const String& TSShape::getNodeName( S32 nodeIndex ) const
|
|
{
|
|
S32 nameIdx = nodes[nodeIndex].nameIndex;
|
|
if ( nameIdx < 0 )
|
|
return String::EmptyString;
|
|
|
|
return names[nameIdx];
|
|
}
|
|
|
|
const String& TSShape::getSequenceName( S32 seqIndex ) const
|
|
{
|
|
AssertFatal(seqIndex >= 0 && seqIndex<sequences.size(),"TSShape::getSequenceName index beyond range");
|
|
|
|
S32 nameIdx = sequences[seqIndex].nameIndex;
|
|
if ( nameIdx < 0 )
|
|
return String::EmptyString;
|
|
|
|
return names[nameIdx];
|
|
}
|
|
|
|
S32 TSShape::findName(const String &name) const
|
|
{
|
|
for (S32 i=0; i<names.size(); i++)
|
|
{
|
|
if (names[i].equal( name, String::NoCase ))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
const String& TSShape::getTargetName( S32 mapToNameIndex ) const
|
|
{
|
|
S32 targetCount = materialList->getMaterialNameList().size();
|
|
|
|
if(mapToNameIndex < 0 || mapToNameIndex >= targetCount)
|
|
return String::EmptyString;
|
|
|
|
return materialList->getMaterialNameList()[mapToNameIndex];
|
|
}
|
|
|
|
S32 TSShape::getTargetCount() const
|
|
{
|
|
return materialList->getMaterialNameList().size();
|
|
}
|
|
|
|
S32 TSShape::findNode(S32 nameIndex) const
|
|
{
|
|
for (S32 i=0; i<nodes.size(); i++)
|
|
if (nodes[i].nameIndex==nameIndex)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
S32 TSShape::findObject(S32 nameIndex) const
|
|
{
|
|
for (S32 i=0; i<objects.size(); i++)
|
|
if (objects[i].nameIndex==nameIndex)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
S32 TSShape::findDetail(S32 nameIndex) const
|
|
{
|
|
for (S32 i=0; i<details.size(); i++)
|
|
if (details[i].nameIndex==nameIndex)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
S32 TSShape::findDetailBySize(S32 size) const
|
|
{
|
|
for (S32 i=0; i<details.size(); i++)
|
|
if (details[i].size==size)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
S32 TSShape::findSequence(S32 nameIndex) const
|
|
{
|
|
for (S32 i=0; i<sequences.size(); i++)
|
|
if (sequences[i].nameIndex==nameIndex)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
bool TSShape::findMeshIndex(const String& meshName, S32& objIndex, S32& meshIndex)
|
|
{
|
|
// Determine the object name and detail size from the mesh name
|
|
S32 detailSize = 999;
|
|
objIndex = findObject(String::GetTrailingNumber(meshName, detailSize));
|
|
if (objIndex < 0)
|
|
return false;
|
|
|
|
// Determine the subshape this object belongs to
|
|
S32 subShapeIndex = getSubShapeForObject(objIndex);
|
|
AssertFatal(subShapeIndex < subShapeFirstObject.size(), "Could not find subshape for object!");
|
|
|
|
// Get the detail levels for the subshape
|
|
Vector<S32> validDetails;
|
|
getSubShapeDetails(subShapeIndex, validDetails);
|
|
|
|
// Find the detail with the correct size
|
|
for (meshIndex = 0; meshIndex < validDetails.size(); meshIndex++)
|
|
{
|
|
const TSShape::Detail& det = details[validDetails[meshIndex]];
|
|
if (detailSize == det.size)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TSShape::needsBufferUpdate()
|
|
{
|
|
// No buffer? definitely need an update!
|
|
if (mVertexSize == 0 || mShapeVertexData.size == 0)
|
|
return true;
|
|
|
|
// Check if we have modified vertex data
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (!mesh ||
|
|
(mesh->getMeshType() != TSMesh::StandardMeshType &&
|
|
mesh->getMeshType() != TSMesh::SkinMeshType))
|
|
continue;
|
|
|
|
// NOTE: cant use mVertexData.isReady since that might not be init'd at this stage
|
|
if (mesh->mVertSize == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TSMesh* TSShape::findMesh(const String& meshName)
|
|
{
|
|
S32 objIndex, meshIndex;
|
|
if (!findMeshIndex(meshName, objIndex, meshIndex))
|
|
return 0;
|
|
return meshes[objects[objIndex].startMeshIndex + meshIndex];
|
|
}
|
|
|
|
S32 TSShape::getSubShapeForNode(S32 nodeIndex)
|
|
{
|
|
for (S32 i = 0; i < subShapeFirstNode.size(); i++)
|
|
{
|
|
S32 start = subShapeFirstNode[i];
|
|
S32 end = start + subShapeNumNodes[i];
|
|
if ((nodeIndex >= start) && (nodeIndex < end))
|
|
return i;;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
S32 TSShape::getSubShapeForObject(S32 objIndex)
|
|
{
|
|
for (S32 i = 0; i < subShapeFirstObject.size(); i++)
|
|
{
|
|
S32 start = subShapeFirstObject[i];
|
|
S32 end = start + subShapeNumObjects[i];
|
|
if ((objIndex >= start) && (objIndex < end))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void TSShape::getSubShapeDetails(S32 subShapeIndex, Vector<S32>& validDetails)
|
|
{
|
|
validDetails.clear();
|
|
for (S32 i = 0; i < details.size(); i++)
|
|
{
|
|
if ((details[i].subShapeNum == subShapeIndex) ||
|
|
(details[i].subShapeNum < 0))
|
|
validDetails.push_back(i);
|
|
}
|
|
}
|
|
|
|
void TSShape::getNodeWorldTransform(S32 nodeIndex, MatrixF* mat) const
|
|
{
|
|
if ( nodeIndex == -1 )
|
|
{
|
|
mat->identity();
|
|
}
|
|
else
|
|
{
|
|
// Calculate the world transform of the given node
|
|
defaultRotations[nodeIndex].getQuatF().setMatrix(mat);
|
|
mat->setPosition(defaultTranslations[nodeIndex]);
|
|
|
|
S32 parentIndex = nodes[nodeIndex].parentIndex;
|
|
while (parentIndex != -1)
|
|
{
|
|
MatrixF mat2(*mat);
|
|
defaultRotations[parentIndex].getQuatF().setMatrix(mat);
|
|
mat->setPosition(defaultTranslations[parentIndex]);
|
|
mat->mul(mat2);
|
|
|
|
parentIndex = nodes[parentIndex].parentIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShape::getNodeObjects(S32 nodeIndex, Vector<S32>& nodeObjects)
|
|
{
|
|
for (S32 i = 0; i < objects.size(); i++)
|
|
{
|
|
if ((nodeIndex == -1) || (objects[i].nodeIndex == nodeIndex))
|
|
nodeObjects.push_back(i);
|
|
}
|
|
}
|
|
|
|
void TSShape::getNodeChildren(S32 nodeIndex, Vector<S32>& nodeChildren)
|
|
{
|
|
for (S32 i = 0; i < nodes.size(); i++)
|
|
{
|
|
if (nodes[i].parentIndex == nodeIndex)
|
|
nodeChildren.push_back(i);
|
|
}
|
|
}
|
|
|
|
void TSShape::getObjectDetails(S32 objIndex, Vector<S32>& objDetails)
|
|
{
|
|
// Get the detail levels for this subshape
|
|
Vector<S32> validDetails;
|
|
getSubShapeDetails(getSubShapeForObject(objIndex), validDetails);
|
|
|
|
// Get the non-null details for this object
|
|
const TSShape::Object& obj = objects[objIndex];
|
|
for (S32 i = 0; i < obj.numMeshes; i++)
|
|
{
|
|
if (meshes[obj.startMeshIndex + i])
|
|
objDetails.push_back(validDetails[i]);
|
|
}
|
|
}
|
|
|
|
void TSShape::init()
|
|
{
|
|
initObjects();
|
|
initVertexFeatures();
|
|
initMaterialList();
|
|
mNeedReinit = false;
|
|
}
|
|
|
|
void TSShape::initObjects()
|
|
{
|
|
S32 numSubShapes = subShapeFirstNode.size();
|
|
AssertFatal(numSubShapes == subShapeFirstObject.size(), "TSShape::initObjects");
|
|
|
|
S32 i, j;
|
|
|
|
// set up parent/child relationships on nodes and objects
|
|
for (i = 0; i<nodes.size(); i++)
|
|
nodes[i].firstObject = nodes[i].firstChild = nodes[i].nextSibling = -1;
|
|
for (i = 0; i<nodes.size(); i++)
|
|
{
|
|
S32 parentIndex = nodes[i].parentIndex;
|
|
if (parentIndex >= 0)
|
|
{
|
|
if (nodes[parentIndex].firstChild<0)
|
|
nodes[parentIndex].firstChild = i;
|
|
else
|
|
{
|
|
S32 child = nodes[parentIndex].firstChild;
|
|
while (nodes[child].nextSibling >= 0)
|
|
child = nodes[child].nextSibling;
|
|
nodes[child].nextSibling = i;
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i<objects.size(); i++)
|
|
{
|
|
objects[i].nextSibling = -1;
|
|
|
|
S32 nodeIndex = objects[i].nodeIndex;
|
|
if (nodeIndex >= 0)
|
|
{
|
|
if (nodes[nodeIndex].firstObject<0)
|
|
nodes[nodeIndex].firstObject = i;
|
|
else
|
|
{
|
|
S32 objectIndex = nodes[nodeIndex].firstObject;
|
|
while (objects[objectIndex].nextSibling >= 0)
|
|
objectIndex = objects[objectIndex].nextSibling;
|
|
objects[objectIndex].nextSibling = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
mFlags = 0;
|
|
for (i = 0; i<sequences.size(); i++)
|
|
{
|
|
if (!sequences[i].animatesScale())
|
|
continue;
|
|
|
|
U32 curVal = mFlags & AnyScale;
|
|
U32 newVal = sequences[i].flags & AnyScale;
|
|
mFlags &= ~(AnyScale);
|
|
mFlags |= getMax(curVal, newVal); // take the larger value (can only convert upwards)
|
|
}
|
|
|
|
// set up alphaIn and alphaOut vectors...
|
|
alphaIn.setSize(details.size());
|
|
alphaOut.setSize(details.size());
|
|
|
|
for (i = 0; i<details.size(); i++)
|
|
{
|
|
if (details[i].size<0)
|
|
{
|
|
// we don't care...
|
|
alphaIn[i] = 0.0f;
|
|
alphaOut[i] = 0.0f;
|
|
}
|
|
else if (i + 1 == details.size() || details[i + 1].size<0)
|
|
{
|
|
alphaIn[i] = 0.0f;
|
|
alphaOut[i] = smAlphaOutLastDetail;
|
|
}
|
|
else
|
|
{
|
|
if (details[i + 1].subShapeNum<0)
|
|
{
|
|
// following detail is a billboard detail...treat special...
|
|
alphaIn[i] = smAlphaInBillboard;
|
|
alphaOut[i] = smAlphaOutBillboard;
|
|
}
|
|
else
|
|
{
|
|
// next detail is normal detail
|
|
alphaIn[i] = smAlphaInDefault;
|
|
alphaOut[i] = smAlphaOutDefault;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = mSmallestVisibleDL - 1; i >= 0; i--)
|
|
{
|
|
if (i<smNumSkipLoadDetails)
|
|
{
|
|
// this detail level renders when pixel size
|
|
// is larger than our cap...zap all the meshes and decals
|
|
// associated with it and use the next detail level
|
|
// instead...
|
|
S32 ss = details[i].subShapeNum;
|
|
S32 od = details[i].objectDetailNum;
|
|
|
|
if (ss == details[i + 1].subShapeNum && od == details[i + 1].objectDetailNum)
|
|
// doh! already done this one (init can be called multiple times on same shape due
|
|
// to sequence importing).
|
|
continue;
|
|
details[i].subShapeNum = details[i + 1].subShapeNum;
|
|
details[i].objectDetailNum = details[i + 1].objectDetailNum;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i<details.size(); i++)
|
|
{
|
|
S32 count = 0;
|
|
S32 ss = details[i].subShapeNum;
|
|
S32 od = details[i].objectDetailNum;
|
|
if (ss<0)
|
|
{
|
|
// billboard detail...
|
|
details[i].polyCount = 2;
|
|
continue;
|
|
}
|
|
S32 start = subShapeFirstObject[ss];
|
|
S32 end = start + subShapeNumObjects[ss];
|
|
for (j = start; j<end; j++)
|
|
{
|
|
Object & obj = objects[j];
|
|
if (od<obj.numMeshes)
|
|
{
|
|
TSMesh * mesh = meshes[obj.startMeshIndex + od];
|
|
count += mesh ? mesh->getNumPolys() : 0;
|
|
}
|
|
}
|
|
details[i].polyCount = count;
|
|
}
|
|
|
|
// Init the collision accelerator array. Note that we don't compute the
|
|
// accelerators until the app requests them
|
|
{
|
|
S32 dca;
|
|
for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
|
|
{
|
|
ConvexHullAccelerator* accel = detailCollisionAccelerators[dca];
|
|
if (accel != NULL) {
|
|
delete[] accel->vertexList;
|
|
delete[] accel->normalList;
|
|
for (S32 vertID = 0; vertID < accel->numVerts; vertID++)
|
|
delete[] accel->emitStrings[vertID];
|
|
delete[] accel->emitStrings;
|
|
delete accel;
|
|
}
|
|
}
|
|
|
|
detailCollisionAccelerators.setSize(details.size());
|
|
for (dca = 0; dca < detailCollisionAccelerators.size(); dca++)
|
|
detailCollisionAccelerators[dca] = NULL;
|
|
}
|
|
|
|
// Assign mesh parents & format
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (!mesh)
|
|
continue;
|
|
|
|
if (mesh->mParentMesh >= meshes.size())
|
|
{
|
|
Con::warnf("Mesh %i has a bad parentMeshObject (%i)", iter - meshes.begin(), mesh->mParentMesh);
|
|
}
|
|
|
|
if (mesh->mParentMesh >= 0 && mesh->mParentMesh < meshes.size())
|
|
{
|
|
mesh->mParentMeshObject = meshes[mesh->mParentMesh];
|
|
}
|
|
else
|
|
{
|
|
mesh->mParentMeshObject = NULL;
|
|
}
|
|
|
|
mesh->mVertexFormat = &mVertexFormat;
|
|
}
|
|
}
|
|
|
|
void TSShape::initVertexBuffers()
|
|
{
|
|
// Assumes mVertexData is valid
|
|
if (!mShapeVertexData.vertexDataReady)
|
|
{
|
|
AssertFatal(false, "WTF");
|
|
}
|
|
|
|
U32 destIndices = 0;
|
|
U32 destPrims = 0;
|
|
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (!mesh ||
|
|
(mesh->getMeshType() != TSMesh::StandardMeshType &&
|
|
mesh->getMeshType() != TSMesh::SkinMeshType))
|
|
continue;
|
|
|
|
destIndices += mesh->mIndices.size();
|
|
destPrims += mesh->mPrimitives.size();
|
|
}
|
|
|
|
// For HW skinning we can just use the static buffer
|
|
if (TSShape::smUseHardwareSkinning)
|
|
{
|
|
getVertexBuffer(mShapeVertexBuffer, GFXBufferTypeStatic);
|
|
}
|
|
|
|
// Also the IBO
|
|
mShapeVertexIndices.set(GFX, destIndices, destPrims, GFXBufferTypeStatic);
|
|
U16 *indicesStart = NULL;
|
|
mShapeVertexIndices.lock(&indicesStart, NULL);
|
|
U16 *ibIndices = indicesStart;
|
|
GFXPrimitive *piInput = mShapeVertexIndices->mPrimitiveArray;
|
|
U32 vertStart = 0;
|
|
U32 primStart = 0;
|
|
U32 indStart = 0;
|
|
|
|
// Create VBO
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (!mesh ||
|
|
(mesh->getMeshType() != TSMesh::StandardMeshType &&
|
|
mesh->getMeshType() != TSMesh::SkinMeshType))
|
|
continue;
|
|
|
|
// Make the offset vbo
|
|
mesh->mPrimBufferOffset = primStart;
|
|
|
|
// Dump primitives to locked buffer
|
|
mesh->dumpPrimitives(vertStart, indStart, piInput, ibIndices);
|
|
|
|
AssertFatal(mesh->mVertOffset / mVertexSize == vertStart, "offset mismatch");
|
|
|
|
vertStart += mesh->mNumVerts;
|
|
primStart += mesh->mPrimitives.size();
|
|
indStart += mesh->mIndices.size();
|
|
|
|
mesh->mVB = mShapeVertexBuffer;
|
|
mesh->mPB = mShapeVertexIndices;
|
|
|
|
// Advance
|
|
piInput += mesh->mPrimitives.size();
|
|
ibIndices += mesh->mIndices.size();
|
|
|
|
if (TSSkinMesh::smDebugSkinVerts && mesh->getMeshType() == TSMesh::SkinMeshType)
|
|
{
|
|
static_cast<TSSkinMesh*>(mesh)->printVerts();
|
|
}
|
|
}
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
// Verify prims
|
|
if (TSSkinMesh::smDebugSkinVerts)
|
|
{
|
|
U32 vertsInBuffer = mShapeVertexData.size / mVertexSize;
|
|
U32 indsInBuffer = ibIndices - indicesStart;
|
|
|
|
for (U32 primID = 0; primID < primStart; primID++)
|
|
{
|
|
GFXPrimitive &prim = mShapeVertexIndices->mPrimitiveArray[primID];
|
|
|
|
if (prim.type != GFXTriangleList && prim.type != GFXTriangleStrip)
|
|
{
|
|
AssertFatal(false, "Unexpected triangle list");
|
|
}
|
|
|
|
if (prim.type == GFXTriangleStrip)
|
|
continue;
|
|
|
|
AssertFatal(prim.startVertex < vertsInBuffer, "wrong start vertex");
|
|
AssertFatal((prim.startVertex + prim.numVertices) <= vertsInBuffer, "too many verts");
|
|
AssertFatal(prim.startIndex + (prim.numPrimitives * 3) <= indsInBuffer, "too many inds");
|
|
|
|
for (U32 i = prim.startIndex; i < prim.startIndex + (prim.numPrimitives * 3); i++)
|
|
{
|
|
if (indicesStart[i] >= vertsInBuffer)
|
|
{
|
|
AssertFatal(false, "vert not in buffer");
|
|
}
|
|
U16 idx = indicesStart[i];
|
|
if (idx < prim.minIndex)
|
|
{
|
|
AssertFatal(false, "index out of minIndex range");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
mShapeVertexIndices.unlock();
|
|
}
|
|
|
|
void TSShape::getVertexBuffer(TSVertexBufferHandle &vb, GFXBufferType bufferType)
|
|
{
|
|
vb.set(GFX, mVertexSize, &mVertexFormat, mShapeVertexData.size / mVertexSize, bufferType);
|
|
|
|
U8 *vertPtr = vb.lock();
|
|
dMemcpy(vertPtr, mShapeVertexData.base, mShapeVertexData.size);
|
|
vb.unlock();
|
|
}
|
|
|
|
void TSShape::initVertexBufferPointers()
|
|
{
|
|
if (mBasicVertexFormat.vertexSize == -1)
|
|
return;
|
|
AssertFatal(mVertexSize == mBasicVertexFormat.vertexSize, "vertex size mismatch");
|
|
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (mesh &&
|
|
(mesh->getMeshType() == TSMesh::StandardMeshType ||
|
|
mesh->getMeshType() == TSMesh::SkinMeshType))
|
|
{
|
|
// Set buffer
|
|
AssertFatal(mesh->mNumVerts == 0 || mesh->mNumVerts >= mesh->vertsPerFrame, "invalid verts per frame");
|
|
if (mesh->mVertSize > 0 && !mesh->mVertexData.isReady())
|
|
{
|
|
U32 boneOffset = 0;
|
|
U32 texCoordOffset = 0;
|
|
AssertFatal(mesh->mVertSize == mVertexFormat.getSizeInBytes(), "mismatch in format size");
|
|
|
|
if (mBasicVertexFormat.boneOffset >= 0)
|
|
{
|
|
boneOffset = mBasicVertexFormat.boneOffset;
|
|
}
|
|
|
|
if (mBasicVertexFormat.texCoordOffset >= 0)
|
|
{
|
|
texCoordOffset = mBasicVertexFormat.texCoordOffset;
|
|
}
|
|
|
|
// Initialize the vertex data
|
|
mesh->mVertexData.set(mShapeVertexData.base + mesh->mVertOffset, mesh->mVertSize, mesh->mNumVerts, texCoordOffset, boneOffset, false);
|
|
mesh->mVertexData.setReady(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TSShape::initVertexFeatures()
|
|
{
|
|
|
|
if (!needsBufferUpdate())
|
|
{
|
|
// Init format from basic format
|
|
mVertexFormat.clear();
|
|
mBasicVertexFormat.getFormat(mVertexFormat);
|
|
mVertexSize = mVertexFormat.getSizeInBytes();
|
|
|
|
initVertexBufferPointers();
|
|
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (mesh &&
|
|
(mesh->getMeshType() == TSMesh::SkinMeshType))
|
|
{
|
|
static_cast<TSSkinMesh*>(mesh)->createSkinBatchData();
|
|
}
|
|
}
|
|
|
|
// Make sure VBO is init'd
|
|
initVertexBuffers();
|
|
return;
|
|
}
|
|
|
|
// Cleanout VBO
|
|
mShapeVertexBuffer = NULL;
|
|
|
|
// Make sure mesh has verts stored in mesh data, we're recreating the buffer
|
|
TSBasicVertexFormat basicFormat;
|
|
|
|
initVertexBufferPointers();
|
|
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (mesh &&
|
|
(mesh->getMeshType() == TSMesh::StandardMeshType ||
|
|
mesh->getMeshType() == TSMesh::SkinMeshType))
|
|
{
|
|
// Make sure we have everything in the vert lists
|
|
mesh->makeEditable();
|
|
|
|
// We need the skin batching data here to determine bone counts
|
|
if (mesh->getMeshType() == TSMesh::SkinMeshType)
|
|
{
|
|
static_cast<TSSkinMesh*>(mesh)->createSkinBatchData();
|
|
}
|
|
|
|
basicFormat.addMeshRequirements(mesh);
|
|
}
|
|
}
|
|
|
|
mVertexFormat.clear();
|
|
mBasicVertexFormat = basicFormat;
|
|
mBasicVertexFormat.getFormat(mVertexFormat);
|
|
mBasicVertexFormat.vertexSize = mVertexFormat.getSizeInBytes();
|
|
mVertexSize = mBasicVertexFormat.vertexSize;
|
|
|
|
U32 destVertex = 0;
|
|
U32 destIndices = 0;
|
|
|
|
// Go fix up meshes to include defaults for optional features
|
|
// and initialize them if they're not a skin mesh.
|
|
U32 count = 0;
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
if (!mesh ||
|
|
(mesh->getMeshType() != TSMesh::StandardMeshType &&
|
|
mesh->getMeshType() != TSMesh::SkinMeshType))
|
|
continue;
|
|
|
|
mesh->mVertSize = mVertexSize;
|
|
mesh->mVertOffset = destVertex;
|
|
|
|
destVertex += mesh->mVertSize * mesh->getNumVerts();
|
|
destIndices += mesh->mIndices.size();
|
|
|
|
count += 1;
|
|
}
|
|
|
|
// Don't set up if we have no meshes
|
|
if (count == 0)
|
|
{
|
|
mShapeVertexData.set(NULL, 0);
|
|
mShapeVertexData.vertexDataReady = false;
|
|
return;
|
|
}
|
|
|
|
// Now we can create the VBO
|
|
mShapeVertexData.set(NULL, 0);
|
|
U8 *vertexData = (U8*)dMalloc_aligned(destVertex, 16);
|
|
U8 *vertexDataPtr = vertexData;
|
|
mShapeVertexData.set(vertexData, destVertex);
|
|
|
|
// Create VBO
|
|
for (Vector<TSMesh*>::iterator iter = meshes.begin(); iter != meshes.end(); iter++)
|
|
{
|
|
TSMesh *mesh = *iter;
|
|
|
|
if (!mesh ||
|
|
(mesh->getMeshType() != TSMesh::StandardMeshType &&
|
|
mesh->getMeshType() != TSMesh::SkinMeshType))
|
|
continue;
|
|
|
|
U32 boneOffset = 0;
|
|
U32 texCoordOffset = 0;
|
|
AssertFatal(mesh->mVertSize == mVertexFormat.getSizeInBytes(), "mismatch in format size");
|
|
|
|
if (mBasicVertexFormat.boneOffset >= 0)
|
|
{
|
|
boneOffset = mBasicVertexFormat.boneOffset;
|
|
}
|
|
|
|
if (mBasicVertexFormat.texCoordOffset >= 0)
|
|
{
|
|
texCoordOffset = mBasicVertexFormat.texCoordOffset;
|
|
}
|
|
|
|
// Dump everything
|
|
mesh->mVertexData.setReady(false);
|
|
mesh->mVertSize = mVertexSize;
|
|
AssertFatal(mesh->mVertOffset == vertexDataPtr - vertexData, "vertex offset mismatch");
|
|
mesh->mNumVerts = mesh->getNumVerts();
|
|
|
|
// Correct bad meshes
|
|
if (mesh->mNumVerts != 0 && mesh->vertsPerFrame > mesh->mNumVerts)
|
|
{
|
|
Con::warnf("Shape mesh has bad vertsPerFrame (%i, should be <= %i)", mesh->vertsPerFrame, mesh->mNumVerts);
|
|
mesh->vertsPerFrame = mesh->mNumVerts;
|
|
}
|
|
|
|
mesh->mVertexData.set(mShapeVertexData.base + mesh->mVertOffset, mesh->mVertSize, mesh->mNumVerts, texCoordOffset, boneOffset, false);
|
|
mesh->convertToVertexData();
|
|
mesh->mVertexData.setReady(true);
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
AssertFatal(mesh->mNumVerts == mesh->mVerts.size(), "vert mismatch");
|
|
for (U32 i = 0; i < mesh->mNumVerts; i++)
|
|
{
|
|
Point3F v1 = mesh->mVerts[i];
|
|
Point3F v2 = mesh->mVertexData.getBase(i).vert();
|
|
AssertFatal(mesh->mVerts[i] == mesh->mVertexData.getBase(i).vert(), "vert data mismatch");
|
|
}
|
|
|
|
if (mesh->getMeshType() == TSMesh::SkinMeshType)
|
|
{
|
|
AssertFatal(mesh->getMaxBonesPerVert() != 0, "Skin mesh has no bones used, very strange!");
|
|
}
|
|
#endif
|
|
|
|
// Advance
|
|
vertexDataPtr += mesh->mVertSize * mesh->mNumVerts;
|
|
|
|
AssertFatal(vertexDataPtr - vertexData <= destVertex, "Vertex data overflow");
|
|
}
|
|
|
|
mShapeVertexData.vertexDataReady = true;
|
|
|
|
initVertexBuffers();
|
|
}
|
|
|
|
void TSShape::setupBillboardDetails( const String &cachePath )
|
|
{
|
|
// set up billboard details -- only do this once, meaning that
|
|
// if we add a sequence to the shape we don't redo the billboard
|
|
// details...
|
|
if ( !billboardDetails.empty() )
|
|
return;
|
|
|
|
for ( U32 i=0; i < details.size(); i++ )
|
|
{
|
|
const Detail &det = details[i];
|
|
|
|
if ( det.subShapeNum >= 0 )
|
|
continue; // not a billboard detail
|
|
|
|
while (billboardDetails.size() <= i )
|
|
billboardDetails.push_back(NULL);
|
|
|
|
billboardDetails[i] = new TSLastDetail( this,
|
|
cachePath,
|
|
det.bbEquatorSteps,
|
|
det.bbPolarSteps,
|
|
det.bbPolarAngle,
|
|
det.bbIncludePoles,
|
|
det.bbDetailLevel,
|
|
det.bbDimension );
|
|
|
|
billboardDetails[i]->update();
|
|
}
|
|
}
|
|
|
|
void TSShape::setupBillboardDetails(const String& cachePath, const String& diffsePath, const String& normalPath)
|
|
{
|
|
// set up billboard details -- only do this once, meaning that
|
|
// if we add a sequence to the shape we don't redo the billboard
|
|
// details...
|
|
if (!billboardDetails.empty())
|
|
return;
|
|
|
|
for (U32 i = 0; i < details.size(); i++)
|
|
{
|
|
const Detail& det = details[i];
|
|
|
|
if (det.subShapeNum >= 0)
|
|
continue; // not a billboard detail
|
|
|
|
while (billboardDetails.size() <= i)
|
|
billboardDetails.push_back(NULL);
|
|
|
|
billboardDetails[i] = new TSLastDetail(this,
|
|
cachePath,
|
|
diffsePath,
|
|
normalPath,
|
|
det.bbEquatorSteps,
|
|
det.bbPolarSteps,
|
|
det.bbPolarAngle,
|
|
det.bbIncludePoles,
|
|
det.bbDetailLevel,
|
|
det.bbDimension);
|
|
|
|
billboardDetails[i]->update();
|
|
}
|
|
}
|
|
|
|
void TSShape::initMaterialList()
|
|
{
|
|
S32 numSubShapes = subShapeFirstObject.size();
|
|
#if defined(TORQUE_MAX_LIB)
|
|
subShapeFirstTranslucentObject.setSize(numSubShapes);
|
|
#endif
|
|
|
|
S32 i,j,k;
|
|
// for each subshape, find the first translucent object
|
|
// also, while we're at it, set mHasTranslucency
|
|
for (S32 ss = 0; ss<numSubShapes; ss++)
|
|
{
|
|
S32 start = subShapeFirstObject[ss];
|
|
S32 end = subShapeNumObjects[ss];
|
|
subShapeFirstTranslucentObject[ss] = end;
|
|
for (i=start; i<end; i++)
|
|
{
|
|
// check to see if this object has translucency
|
|
Object & obj = objects[i];
|
|
for (j=0; j<obj.numMeshes; j++)
|
|
{
|
|
TSMesh * mesh = meshes[obj.startMeshIndex+j];
|
|
if (!mesh)
|
|
continue;
|
|
|
|
for (k=0; k<mesh->mPrimitives.size(); k++)
|
|
{
|
|
if (mesh->mPrimitives[k].matIndex & TSDrawPrimitive::NoMaterial)
|
|
continue;
|
|
S32 flags = materialList->getFlags(mesh->mPrimitives[k].matIndex & TSDrawPrimitive::MaterialMask);
|
|
if (flags & TSMaterialList::AuxiliaryMap)
|
|
continue;
|
|
if (flags & TSMaterialList::Translucent)
|
|
{
|
|
mFlags |= HasTranslucency;
|
|
subShapeFirstTranslucentObject[ss] = i;
|
|
break;
|
|
}
|
|
}
|
|
if (k!=mesh->mPrimitives.size())
|
|
break;
|
|
}
|
|
if (j!=obj.numMeshes)
|
|
break;
|
|
}
|
|
if (i!=end)
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
bool TSShape::preloadMaterialList(const Torque::Path &path)
|
|
{
|
|
if (materialList)
|
|
materialList->setTextureLookupPath(path.getPath());
|
|
return true;
|
|
}
|
|
|
|
bool TSShape::buildConvexHull(S32 dl) const
|
|
{
|
|
AssertFatal(dl>=0 && dl<details.size(),"TSShape::buildConvexHull: detail out of range");
|
|
|
|
bool ok = true;
|
|
|
|
const Detail & detail = details[dl];
|
|
S32 ss = detail.subShapeNum;
|
|
S32 od = detail.objectDetailNum;
|
|
|
|
S32 start = subShapeFirstObject[ss];
|
|
S32 end = subShapeNumObjects[ss];
|
|
for (S32 i=start; i<end; i++)
|
|
{
|
|
TSMesh * mesh = meshes[objects[i].startMeshIndex+od];
|
|
if (!mesh)
|
|
continue;
|
|
ok &= mesh->buildConvexHull();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
Vector<MatrixF> gTempNodeTransforms(__FILE__, __LINE__);
|
|
|
|
void TSShape::computeBounds(S32 dl, Box3F & bounds) const
|
|
{
|
|
// if dl==-1, nothing to do
|
|
if (dl==-1)
|
|
return;
|
|
|
|
AssertFatal(dl>=0 && dl<details.size(),"TSShapeInstance::computeBounds");
|
|
|
|
// get subshape and object detail
|
|
const TSDetail * detail = &details[dl];
|
|
S32 ss = detail->subShapeNum;
|
|
S32 od = detail->objectDetailNum;
|
|
|
|
// If we have no subshapes then there is
|
|
// no valid bounds for this detail level.
|
|
if ( ss < 0 )
|
|
return;
|
|
|
|
// set up temporary storage for non-local transforms...
|
|
S32 i;
|
|
S32 start = subShapeFirstNode[ss];
|
|
S32 end = subShapeNumNodes[ss] + start;
|
|
gTempNodeTransforms.setSize(end-start);
|
|
for (i=start; i<end; i++)
|
|
{
|
|
MatrixF mat;
|
|
QuatF q;
|
|
TSTransform::setMatrix(defaultRotations[i].getQuatF(&q),defaultTranslations[i],&mat);
|
|
if (nodes[i].parentIndex>=0)
|
|
gTempNodeTransforms[i-start].mul(gTempNodeTransforms[nodes[i].parentIndex-start],mat);
|
|
else
|
|
gTempNodeTransforms[i-start] = mat;
|
|
}
|
|
|
|
// run through objects and updating bounds as we go
|
|
bounds.minExtents.set( 10E30f, 10E30f, 10E30f);
|
|
bounds.maxExtents.set(-10E30f,-10E30f,-10E30f);
|
|
Box3F box;
|
|
start = subShapeFirstObject[ss];
|
|
end = subShapeNumObjects[ss] + start;
|
|
for (i=start; i<end; i++)
|
|
{
|
|
const Object * object = &objects[i];
|
|
TSMesh * mesh = od<object->numMeshes ? meshes[object->startMeshIndex+od] : NULL;
|
|
if (mesh)
|
|
{
|
|
static MatrixF idMat(true);
|
|
if (object->nodeIndex<0)
|
|
mesh->computeBounds(idMat,box);
|
|
else
|
|
mesh->computeBounds(gTempNodeTransforms[object->nodeIndex-start],box);
|
|
bounds.minExtents.setMin(box.minExtents);
|
|
bounds.maxExtents.setMax(box.maxExtents);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSShapeAlloc TSShape::smTSAlloc;
|
|
|
|
#define tsalloc TSShape::smTSAlloc
|
|
|
|
|
|
// messy stuff: check to see if we should "skip" meshNum
|
|
// this assumes that meshes for a given object are in a row
|
|
// skipDL is the lowest detail number we keep (i.e., the # of details we skip)
|
|
bool TSShape::checkSkip(S32 meshNum, S32 & curObject, S32 skipDL)
|
|
{
|
|
if (skipDL==0)
|
|
// easy out...
|
|
return false;
|
|
|
|
// skip detail level exists on this subShape
|
|
S32 skipSS = details[skipDL].subShapeNum;
|
|
|
|
if (curObject<objects.size())
|
|
{
|
|
S32 start = objects[curObject].startMeshIndex;
|
|
if (meshNum>=start)
|
|
{
|
|
// we are either from this object, the next object, or a decal
|
|
if (meshNum < start + objects[curObject].numMeshes)
|
|
{
|
|
// this object...
|
|
if (subShapeFirstObject[skipSS]>curObject)
|
|
// haven't reached this subshape yet
|
|
return true;
|
|
if (skipSS+1==subShapeFirstObject.size() || curObject<subShapeFirstObject[skipSS+1])
|
|
// curObject is on subshape of skip detail...make sure it's after skipDL
|
|
return (meshNum-start<details[skipDL].objectDetailNum);
|
|
// if we get here, then curObject occurs on subShape after skip detail (so keep it)
|
|
return false;
|
|
}
|
|
else
|
|
// advance object, try again
|
|
return checkSkip(meshNum,++curObject,skipDL);
|
|
}
|
|
}
|
|
|
|
AssertFatal(0,"TSShape::checkSkip: assertion failed");
|
|
return false;
|
|
}
|
|
|
|
void TSShape::assembleShape()
|
|
{
|
|
S32 i,j;
|
|
|
|
// get counts...
|
|
S32 numNodes = tsalloc.get32();
|
|
S32 numObjects = tsalloc.get32();
|
|
S32 numDecals = tsalloc.get32();
|
|
S32 numSubShapes = tsalloc.get32();
|
|
S32 numIflMaterials = tsalloc.get32();
|
|
S32 numNodeRots;
|
|
S32 numNodeTrans;
|
|
S32 numNodeUniformScales;
|
|
S32 numNodeAlignedScales;
|
|
S32 numNodeArbitraryScales;
|
|
if (smReadVersion<22)
|
|
{
|
|
numNodeRots = numNodeTrans = tsalloc.get32() - numNodes;
|
|
numNodeUniformScales = numNodeAlignedScales = numNodeArbitraryScales = 0;
|
|
}
|
|
else
|
|
{
|
|
numNodeRots = tsalloc.get32();
|
|
numNodeTrans = tsalloc.get32();
|
|
numNodeUniformScales = tsalloc.get32();
|
|
numNodeAlignedScales = tsalloc.get32();
|
|
numNodeArbitraryScales = tsalloc.get32();
|
|
}
|
|
S32 numGroundFrames = 0;
|
|
if (smReadVersion>23)
|
|
numGroundFrames = tsalloc.get32();
|
|
S32 numObjectStates = tsalloc.get32();
|
|
S32 numDecalStates = tsalloc.get32();
|
|
S32 numTriggers = tsalloc.get32();
|
|
S32 numDetails = tsalloc.get32();
|
|
S32 numMeshes = tsalloc.get32();
|
|
S32 numSkins = 0;
|
|
if (smReadVersion<23)
|
|
// in later versions, skins are kept with other meshes
|
|
numSkins = tsalloc.get32();
|
|
S32 numNames = tsalloc.get32();
|
|
|
|
// Note that we are recalculating these values later on for safety.
|
|
mSmallestVisibleSize = (F32)tsalloc.get32();
|
|
mSmallestVisibleDL = tsalloc.get32();
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// get bounds...
|
|
tsalloc.get32((S32*)&mRadius,1);
|
|
tsalloc.get32((S32*)&tubeRadius,1);
|
|
tsalloc.get32((S32*)¢er,3);
|
|
tsalloc.get32((S32*)&mBounds,6);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// copy various vectors...
|
|
S32 * ptr32 = tsalloc.copyToShape32(numNodes*5);
|
|
nodes.set(ptr32,numNodes);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
ptr32 = tsalloc.copyToShape32(numObjects*6,true);
|
|
if (!ptr32)
|
|
ptr32 = tsalloc.allocShape32(numSkins*6); // pre v23 shapes store skins and meshes separately...no longer
|
|
else
|
|
tsalloc.allocShape32(numSkins*6);
|
|
objects.set(ptr32,numObjects);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// DEPRECATED decals
|
|
ptr32 = tsalloc.getPointer32(numDecals*5);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// DEPRECATED ifl materials
|
|
ptr32 = tsalloc.copyToShape32(numIflMaterials*5);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
ptr32 = tsalloc.copyToShape32(numSubShapes,true);
|
|
subShapeFirstNode.set(ptr32,numSubShapes);
|
|
ptr32 = tsalloc.copyToShape32(numSubShapes,true);
|
|
subShapeFirstObject.set(ptr32,numSubShapes);
|
|
// DEPRECATED subShapeFirstDecal
|
|
ptr32 = tsalloc.getPointer32(numSubShapes);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
ptr32 = tsalloc.copyToShape32(numSubShapes);
|
|
subShapeNumNodes.set(ptr32,numSubShapes);
|
|
ptr32 = tsalloc.copyToShape32(numSubShapes);
|
|
subShapeNumObjects.set(ptr32,numSubShapes);
|
|
// DEPRECATED subShapeNumDecals
|
|
ptr32 = tsalloc.getPointer32(numSubShapes);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
ptr32 = tsalloc.allocShape32(numSubShapes);
|
|
subShapeFirstTranslucentObject.set(ptr32,numSubShapes);
|
|
|
|
// get default translation and rotation
|
|
S16 * ptr16 = tsalloc.allocShape16(0);
|
|
for (i=0;i<numNodes;i++)
|
|
tsalloc.copyToShape16(4);
|
|
defaultRotations.set(ptr16,numNodes);
|
|
tsalloc.align32();
|
|
ptr32 = tsalloc.allocShape32(0);
|
|
for (i=0;i<numNodes;i++)
|
|
{
|
|
tsalloc.copyToShape32(3);
|
|
tsalloc.copyToShape32(sizeof(Point3F)-12); // handle alignment issues w/ point3f
|
|
}
|
|
defaultTranslations.set(ptr32,numNodes);
|
|
|
|
// get any node sequence data stored in shape
|
|
nodeTranslations.setSize(numNodeTrans);
|
|
for (i=0;i<numNodeTrans;i++)
|
|
tsalloc.get32((S32*)&nodeTranslations[i],3);
|
|
nodeRotations.setSize(numNodeRots);
|
|
for (i=0;i<numNodeRots;i++)
|
|
tsalloc.get16((S16*)&nodeRotations[i],4);
|
|
tsalloc.align32();
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
if (smReadVersion>21)
|
|
{
|
|
// more node sequence data...scale
|
|
nodeUniformScales.setSize(numNodeUniformScales);
|
|
for (i=0;i<numNodeUniformScales;i++)
|
|
tsalloc.get32((S32*)&nodeUniformScales[i],1);
|
|
nodeAlignedScales.setSize(numNodeAlignedScales);
|
|
for (i=0;i<numNodeAlignedScales;i++)
|
|
tsalloc.get32((S32*)&nodeAlignedScales[i],3);
|
|
nodeArbitraryScaleFactors.setSize(numNodeArbitraryScales);
|
|
for (i=0;i<numNodeArbitraryScales;i++)
|
|
tsalloc.get32((S32*)&nodeArbitraryScaleFactors[i],3);
|
|
nodeArbitraryScaleRots.setSize(numNodeArbitraryScales);
|
|
for (i=0;i<numNodeArbitraryScales;i++)
|
|
tsalloc.get16((S16*)&nodeArbitraryScaleRots[i],4);
|
|
tsalloc.align32();
|
|
|
|
tsalloc.checkGuard();
|
|
}
|
|
|
|
// old shapes need ground transforms moved to ground arrays...but only do it once
|
|
if (smReadVersion<22 && tsalloc.allocShape32(0))
|
|
{
|
|
for (i=0; 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 groundFrm =0; groundFrm<seq.numGroundFrames; groundFrm++)
|
|
{
|
|
groundTranslations[groundFrm +oldSz] = nodeTranslations[seq.firstGroundFrame+ groundFrm -numNodes];
|
|
groundRotations[groundFrm +oldSz] = nodeRotations[seq.firstGroundFrame+ groundFrm -numNodes];
|
|
}
|
|
seq.firstGroundFrame = oldSz;
|
|
seq.baseTranslation -= numNodes;
|
|
seq.baseRotation -= numNodes;
|
|
seq.baseScale = 0; // not used on older shapes...but keep it clean
|
|
}
|
|
}
|
|
|
|
// version 22 & 23 shapes accidentally had no ground transforms, and ground for
|
|
// earlier shapes is handled just above, so...
|
|
if (smReadVersion>23)
|
|
{
|
|
groundTranslations.setSize(numGroundFrames);
|
|
for (i=0;i<numGroundFrames;i++)
|
|
tsalloc.get32((S32*)&groundTranslations[i],3);
|
|
groundRotations.setSize(numGroundFrames);
|
|
for (i=0;i<numGroundFrames;i++)
|
|
tsalloc.get16((S16*)&groundRotations[i],4);
|
|
tsalloc.align32();
|
|
|
|
tsalloc.checkGuard();
|
|
}
|
|
|
|
// object states
|
|
ptr32 = tsalloc.copyToShape32(numObjectStates*3);
|
|
objectStates.set(ptr32,numObjectStates);
|
|
tsalloc.allocShape32(numSkins*3); // provide buffer after objectStates for older shapes
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// DEPRECATED decal states
|
|
ptr32 = tsalloc.getPointer32(numDecalStates);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// frame triggers
|
|
ptr32 = tsalloc.getPointer32(numTriggers*2);
|
|
triggers.setSize(numTriggers);
|
|
dMemcpy(triggers.address(),ptr32,sizeof(S32)*numTriggers*2);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// details
|
|
if ( smReadVersion >= 26 )
|
|
{
|
|
U32 alignedSize32 = sizeof( Detail ) / 4;
|
|
ptr32 = tsalloc.copyToShape32( numDetails * alignedSize32, true );
|
|
details.set( ptr32, numDetails );
|
|
}
|
|
else
|
|
{
|
|
// Previous to version 26 the Detail structure
|
|
// only contained the first 7 values...
|
|
//
|
|
// struct Detail
|
|
// {
|
|
// S32 nameIndex;
|
|
// S32 subShapeNum;
|
|
// S32 objectDetailNum;
|
|
// F32 size;
|
|
// F32 averageError;
|
|
// F32 maxError;
|
|
// S32 polyCount;
|
|
// };
|
|
//
|
|
// In the code below we're reading just these 7 values and
|
|
// copying them to the new larger structure.
|
|
|
|
ptr32 = tsalloc.copyToShape32( numDetails * 7, true );
|
|
|
|
details.setSize( numDetails );
|
|
for ( U32 detID = 0; detID < details.size(); detID++, ptr32 += 7 )
|
|
{
|
|
Detail *det = &(details[detID]);
|
|
|
|
// Clear the struct... we don't want to leave
|
|
// garbage in the parts that are unfilled.
|
|
U32 alignedSize32 = sizeof( Detail );
|
|
dMemset( det, 0, alignedSize32 );
|
|
|
|
// Copy the old struct values over.
|
|
dMemcpy( det, ptr32, 7 * 4 );
|
|
|
|
// If this is an autobillboard then we need to
|
|
// fill in the new part of the struct.
|
|
if ( det->subShapeNum >= 0 )
|
|
continue;
|
|
|
|
S32 lastDetailOpts = det->objectDetailNum;
|
|
det->bbEquatorSteps = lastDetailOpts & 0x7F; // bits 0..6
|
|
det->bbPolarSteps = (lastDetailOpts >> 7) & 0x3F; // bits 7..12
|
|
det->bbPolarAngle = 0.5f * M_PI_F * (1.0f/64.0f) * (F32) (( lastDetailOpts >>13 ) & 0x3F); // bits 13..18
|
|
det->bbDetailLevel = (lastDetailOpts >> 19) & 0x0F; // 19..22
|
|
det->bbDimension = (lastDetailOpts >> 23) & 0xFF; // 23..30
|
|
det->bbIncludePoles = (lastDetailOpts & 0x80000000)!=0; // bit 31
|
|
}
|
|
}
|
|
|
|
// Some DTS exporters (MAX - I'm looking at you!) write garbage into the
|
|
// averageError and maxError values which stops LOD from working correctly.
|
|
// Try to detect and fix it
|
|
for ( U32 erID = 0; erID < details.size(); erID++ )
|
|
{
|
|
if ( ( details[erID].averageError == 0 ) || ( details[erID].averageError > 10000 ) ||
|
|
( details[erID].maxError == 0 ) || ( details[erID].maxError > 10000 ) )
|
|
{
|
|
details[erID].averageError = details[erID].maxError = -1.0f;
|
|
}
|
|
}
|
|
|
|
// We don't trust the value of mSmallestVisibleDL loaded from the dts
|
|
// since some legacy meshes seem to have the wrong value. Recalculate it
|
|
// now that we have the details loaded.
|
|
updateSmallestVisibleDL();
|
|
|
|
S32 skipDL = getMin(mSmallestVisibleDL,smNumSkipLoadDetails);
|
|
if (skipDL < 0)
|
|
skipDL = 0;
|
|
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
if (TSShape::smReadVersion >= 27)
|
|
{
|
|
// Vertex format is set here
|
|
S8 *vboData = NULL;
|
|
S32 vboSize = 0;
|
|
|
|
mBasicVertexFormat.readAlloc(&tsalloc);
|
|
mVertexFormat.clear();
|
|
mBasicVertexFormat.getFormat(mVertexFormat);
|
|
mVertexSize = mVertexFormat.getSizeInBytes();
|
|
|
|
AssertFatal(mVertexSize == mBasicVertexFormat.vertexSize, "vertex size mismatch");
|
|
|
|
vboSize = tsalloc.get32();
|
|
vboData = tsalloc.getPointer8(vboSize);
|
|
|
|
if (tsalloc.getBuffer() && vboSize > 0)
|
|
{
|
|
U8 *vertexData = (U8*)dMalloc_aligned(vboSize, 16);
|
|
dMemcpy(vertexData, vboData, vboSize);
|
|
mShapeVertexData.set(vertexData, vboSize);
|
|
mShapeVertexData.vertexDataReady = true;
|
|
}
|
|
else
|
|
{
|
|
mShapeVertexData.set(NULL, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mShapeVertexData.set(NULL, 0);
|
|
}
|
|
|
|
// about to read in the meshes...first must allocate some scratch space
|
|
S32 scratchSize = getMax(numSkins,numMeshes);
|
|
TSMesh::smVertsList.setSize(scratchSize);
|
|
TSMesh::smTVertsList.setSize(scratchSize);
|
|
|
|
if ( smReadVersion >= 26 )
|
|
{
|
|
TSMesh::smTVerts2List.setSize(scratchSize);
|
|
TSMesh::smColorsList.setSize(scratchSize);
|
|
}
|
|
|
|
TSMesh::smNormsList.setSize(scratchSize);
|
|
TSMesh::smEncodedNormsList.setSize(scratchSize);
|
|
TSMesh::smDataCopied.setSize(scratchSize);
|
|
TSSkinMesh::smInitTransformList.setSize(scratchSize);
|
|
TSSkinMesh::smVertexIndexList.setSize(scratchSize);
|
|
TSSkinMesh::smBoneIndexList.setSize(scratchSize);
|
|
TSSkinMesh::smWeightList.setSize(scratchSize);
|
|
TSSkinMesh::smNodeIndexList.setSize(scratchSize);
|
|
for (i=0; i<numMeshes; i++)
|
|
{
|
|
TSMesh::smVertsList[i]=NULL;
|
|
TSMesh::smTVertsList[i]=NULL;
|
|
|
|
if ( smReadVersion >= 26 )
|
|
{
|
|
TSMesh::smTVerts2List[i] = NULL;
|
|
TSMesh::smColorsList[i] = NULL;
|
|
}
|
|
|
|
TSMesh::smNormsList[i]=NULL;
|
|
TSMesh::smEncodedNormsList[i]=NULL;
|
|
TSMesh::smDataCopied[i]=false;
|
|
TSSkinMesh::smInitTransformList[i] = NULL;
|
|
TSSkinMesh::smVertexIndexList[i] = NULL;
|
|
TSSkinMesh::smBoneIndexList[i] = NULL;
|
|
TSSkinMesh::smWeightList[i] = NULL;
|
|
TSSkinMesh::smNodeIndexList[i] = NULL;
|
|
}
|
|
|
|
// read in the meshes (sans skins)...straightforward read one at a time
|
|
TSMesh **ptrmesh = (TSMesh**)tsalloc.allocShape32((numMeshes + numSkins*numDetails) * (sizeof(TSMesh*) / 4));
|
|
S32 curObject = 0; // for tracking skipped meshes
|
|
for (i=0; i<numMeshes; i++)
|
|
{
|
|
bool skip = checkSkip(i,curObject,skipDL); // skip this mesh?
|
|
S32 meshType = tsalloc.get32();
|
|
if (meshType == TSMesh::DecalMeshType)
|
|
// decal mesh deprecated
|
|
skip = true;
|
|
TSMesh * mesh = TSMesh::assembleMesh(meshType,skip);
|
|
if (ptrmesh)
|
|
{
|
|
ptrmesh[i] = skip ? 0 : mesh;
|
|
}
|
|
|
|
// fill in location of verts, tverts, and normals for detail levels
|
|
if (mesh && meshType!=TSMesh::DecalMeshType)
|
|
{
|
|
TSMesh::smVertsList[i] = mesh->mVerts.address();
|
|
TSMesh::smTVertsList[i] = mesh->mTverts.address();
|
|
if (smReadVersion >= 26)
|
|
{
|
|
TSMesh::smTVerts2List[i] = mesh->mTverts2.address();
|
|
TSMesh::smColorsList[i] = mesh->mColors.address();
|
|
}
|
|
TSMesh::smNormsList[i] = mesh->mNorms.address();
|
|
TSMesh::smEncodedNormsList[i] = mesh->mEncodedNorms.address();
|
|
TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now
|
|
if (meshType==TSMesh::SkinMeshType)
|
|
{
|
|
TSSkinMesh * skin = (TSSkinMesh*)mesh;
|
|
TSMesh::smVertsList[i] = skin->batchData.initialVerts.address();
|
|
TSMesh::smNormsList[i] = skin->batchData.initialNorms.address();
|
|
TSSkinMesh::smInitTransformList[i] = skin->batchData.initialTransforms.address();
|
|
TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address();
|
|
TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address();
|
|
TSSkinMesh::smWeightList[i] = skin->weight.address();
|
|
TSSkinMesh::smNodeIndexList[i] = skin->batchData.nodeIndex.address();
|
|
}
|
|
}
|
|
}
|
|
meshes.set(ptrmesh, numMeshes);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// names
|
|
char * nameBufferStart = (char*)tsalloc.getPointer8(0);
|
|
char * name = nameBufferStart;
|
|
S32 nameBufferSize = 0;
|
|
names.setSize(numNames);
|
|
for (i=0; i<numNames; i++)
|
|
{
|
|
for (j=0; name[j]; j++)
|
|
;
|
|
|
|
names[i] = name;
|
|
nameBufferSize += j + 1;
|
|
name += j + 1;
|
|
}
|
|
|
|
tsalloc.getPointer8(nameBufferSize);
|
|
tsalloc.align32();
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
if (smReadVersion<23)
|
|
{
|
|
// get detail information about skins...
|
|
S32 * detFirstSkin = tsalloc.getPointer32(numDetails);
|
|
S32 * detailNumSkins = tsalloc.getPointer32(numDetails);
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// about to read in skins...clear out scratch space...
|
|
if (numSkins)
|
|
{
|
|
TSSkinMesh::smInitTransformList.setSize(numSkins);
|
|
TSSkinMesh::smVertexIndexList.setSize(numSkins);
|
|
TSSkinMesh::smBoneIndexList.setSize(numSkins);
|
|
TSSkinMesh::smWeightList.setSize(numSkins);
|
|
TSSkinMesh::smNodeIndexList.setSize(numSkins);
|
|
}
|
|
for (i=0; i<numSkins; i++)
|
|
{
|
|
TSMesh::smVertsList[i]=NULL;
|
|
TSMesh::smTVertsList[i]=NULL;
|
|
TSMesh::smNormsList[i]=NULL;
|
|
TSMesh::smEncodedNormsList[i]=NULL;
|
|
TSMesh::smDataCopied[i]=false;
|
|
TSSkinMesh::smInitTransformList[i] = NULL;
|
|
TSSkinMesh::smVertexIndexList[i] = NULL;
|
|
TSSkinMesh::smBoneIndexList[i] = NULL;
|
|
TSSkinMesh::smWeightList[i] = NULL;
|
|
TSSkinMesh::smNodeIndexList[i] = NULL;
|
|
}
|
|
|
|
// skins
|
|
ptr32 = tsalloc.allocShape32(numSkins);
|
|
for (i=0; i<numSkins; i++)
|
|
{
|
|
bool skip = i<detFirstSkin[skipDL];
|
|
TSSkinMesh * skin = (TSSkinMesh*)TSMesh::assembleMesh(TSMesh::SkinMeshType,skip);
|
|
if (meshes.address())
|
|
{
|
|
// add pointer to skin in shapes list of meshes
|
|
// we reserved room for this above...
|
|
meshes.set(meshes.address(),meshes.size()+1);
|
|
meshes[meshes.size()-1] = skip ? NULL : skin;
|
|
}
|
|
|
|
// fill in location of verts, tverts, and normals for shared detail levels
|
|
if (skin)
|
|
{
|
|
TSMesh::smVertsList[i] = skin->batchData.initialVerts.address();
|
|
TSMesh::smTVertsList[i] = skin->mTverts.address();
|
|
TSMesh::smNormsList[i] = skin->batchData.initialNorms.address();
|
|
TSMesh::smEncodedNormsList[i] = skin->mEncodedNorms.address();
|
|
TSMesh::smDataCopied[i] = !skip; // as long as we didn't skip this mesh, the data should be in shape now
|
|
TSSkinMesh::smInitTransformList[i] = skin->batchData.initialTransforms.address();
|
|
TSSkinMesh::smVertexIndexList[i] = skin->vertexIndex.address();
|
|
TSSkinMesh::smBoneIndexList[i] = skin->boneIndex.address();
|
|
TSSkinMesh::smWeightList[i] = skin->weight.address();
|
|
TSSkinMesh::smNodeIndexList[i] = skin->batchData.nodeIndex.address();
|
|
}
|
|
}
|
|
|
|
tsalloc.checkGuard();
|
|
|
|
// we now have skins in mesh list...add skin objects to object list and patch things up
|
|
fixupOldSkins(numMeshes,numSkins,numDetails,detFirstSkin,detailNumSkins);
|
|
}
|
|
|
|
// allocate storage space for some arrays (filled in during Shape::init)...
|
|
ptr32 = tsalloc.allocShape32(numDetails);
|
|
alphaIn.set(ptr32,numDetails);
|
|
ptr32 = tsalloc.allocShape32(numDetails);
|
|
alphaOut.set(ptr32,numDetails);
|
|
}
|
|
|
|
void TSShape::disassembleShape()
|
|
{
|
|
S32 i;
|
|
|
|
// set counts...
|
|
S32 numNodes = tsalloc.set32(nodes.size());
|
|
S32 numObjects = tsalloc.set32(objects.size());
|
|
tsalloc.set32(0); // DEPRECATED decals
|
|
S32 numSubShapes = tsalloc.set32(subShapeFirstNode.size());
|
|
tsalloc.set32(0); // DEPRECATED ifl materials
|
|
S32 numNodeRotations = tsalloc.set32(nodeRotations.size());
|
|
S32 numNodeTranslations = tsalloc.set32(nodeTranslations.size());
|
|
S32 numNodeUniformScales = tsalloc.set32(nodeUniformScales.size());
|
|
S32 numNodeAlignedScales = tsalloc.set32(nodeAlignedScales.size());
|
|
S32 numNodeArbitraryScales = tsalloc.set32(nodeArbitraryScaleFactors.size());
|
|
S32 numGroundFrames = tsalloc.set32(groundTranslations.size());
|
|
S32 numObjectStates = tsalloc.set32(objectStates.size());
|
|
tsalloc.set32(0); // DEPRECATED decals
|
|
S32 numTriggers = tsalloc.set32(triggers.size());
|
|
S32 numDetails = tsalloc.set32(details.size());
|
|
S32 numMeshes = tsalloc.set32(meshes.size());
|
|
S32 numNames = tsalloc.set32(names.size());
|
|
tsalloc.set32((S32)mSmallestVisibleSize);
|
|
tsalloc.set32(mSmallestVisibleDL);
|
|
|
|
tsalloc.setGuard();
|
|
|
|
// get bounds...
|
|
tsalloc.copyToBuffer32((S32*)&mRadius,1);
|
|
tsalloc.copyToBuffer32((S32*)&tubeRadius,1);
|
|
tsalloc.copyToBuffer32((S32*)¢er,3);
|
|
tsalloc.copyToBuffer32((S32*)&mBounds,6);
|
|
|
|
tsalloc.setGuard();
|
|
|
|
// copy various vectors...
|
|
tsalloc.copyToBuffer32((S32*)nodes.address(),numNodes*5);
|
|
tsalloc.setGuard();
|
|
tsalloc.copyToBuffer32((S32*)objects.address(),numObjects*6);
|
|
tsalloc.setGuard();
|
|
// DEPRECATED: no copy decals
|
|
tsalloc.setGuard();
|
|
tsalloc.copyToBuffer32(0,0); // DEPRECATED: ifl materials!
|
|
tsalloc.setGuard();
|
|
tsalloc.copyToBuffer32((S32*)subShapeFirstNode.address(),numSubShapes);
|
|
tsalloc.copyToBuffer32((S32*)subShapeFirstObject.address(),numSubShapes);
|
|
tsalloc.copyToBuffer32(0, numSubShapes); // DEPRECATED: no copy subShapeFirstDecal
|
|
tsalloc.setGuard();
|
|
tsalloc.copyToBuffer32((S32*)subShapeNumNodes.address(),numSubShapes);
|
|
tsalloc.copyToBuffer32((S32*)subShapeNumObjects.address(),numSubShapes);
|
|
tsalloc.copyToBuffer32(0, numSubShapes); // DEPRECATED: no copy subShapeNumDecals
|
|
tsalloc.setGuard();
|
|
|
|
// default transforms...
|
|
tsalloc.copyToBuffer16((S16*)defaultRotations.address(),numNodes*4);
|
|
tsalloc.copyToBuffer32((S32*)defaultTranslations.address(),numNodes*3);
|
|
|
|
// animated transforms...
|
|
tsalloc.copyToBuffer16((S16*)nodeRotations.address(),numNodeRotations*4);
|
|
tsalloc.copyToBuffer32((S32*)nodeTranslations.address(),numNodeTranslations*3);
|
|
|
|
tsalloc.setGuard();
|
|
|
|
// ...with scale
|
|
tsalloc.copyToBuffer32((S32*)nodeUniformScales.address(),numNodeUniformScales);
|
|
tsalloc.copyToBuffer32((S32*)nodeAlignedScales.address(),numNodeAlignedScales*3);
|
|
tsalloc.copyToBuffer32((S32*)nodeArbitraryScaleFactors.address(),numNodeArbitraryScales*3);
|
|
tsalloc.copyToBuffer16((S16*)nodeArbitraryScaleRots.address(),numNodeArbitraryScales*4);
|
|
|
|
tsalloc.setGuard();
|
|
|
|
tsalloc.copyToBuffer32((S32*)groundTranslations.address(),3*numGroundFrames);
|
|
tsalloc.copyToBuffer16((S16*)groundRotations.address(),4*numGroundFrames);
|
|
|
|
tsalloc.setGuard();
|
|
|
|
// object states..
|
|
tsalloc.copyToBuffer32((S32*)objectStates.address(),numObjectStates*3);
|
|
tsalloc.setGuard();
|
|
|
|
// decal states...
|
|
// DEPRECATED (numDecalStates = 0)
|
|
tsalloc.setGuard();
|
|
|
|
// frame triggers
|
|
tsalloc.copyToBuffer32((S32*)triggers.address(),numTriggers*2);
|
|
tsalloc.setGuard();
|
|
|
|
// details
|
|
if (TSShape::smVersion > 25)
|
|
{
|
|
U32 alignedSize32 = sizeof( Detail ) / 4;
|
|
tsalloc.copyToBuffer32((S32*)details.address(),numDetails * alignedSize32 );
|
|
}
|
|
else
|
|
{
|
|
// Legacy details => no explicit autobillboard parameters
|
|
U32 legacyDetailSize32 = 7; // only store the first 7 4-byte values of each detail
|
|
for ( S32 bbID = 0; bbID < details.size(); bbID++ )
|
|
tsalloc.copyToBuffer32( (S32*)&details[bbID], legacyDetailSize32 );
|
|
}
|
|
tsalloc.setGuard();
|
|
|
|
if (TSShape::smVersion >= 27)
|
|
{
|
|
// Vertex format now included with mesh data. Note this doesn't include index data which
|
|
// is constructed directly in the buffer from the meshes
|
|
|
|
mBasicVertexFormat.writeAlloc(&tsalloc);
|
|
|
|
tsalloc.set32(mShapeVertexData.size);
|
|
tsalloc.copyToBuffer8((S8*)mShapeVertexData.base, mShapeVertexData.size);
|
|
}
|
|
|
|
// read in the meshes (sans skins)...
|
|
bool * isMesh = new bool[numMeshes]; // funny business because decals are pretend meshes (legacy issue)
|
|
for (i=0;i<numMeshes;i++)
|
|
isMesh[i]=false;
|
|
for (i=0; i<objects.size(); i++)
|
|
{
|
|
for (S32 j=0; j<objects[i].numMeshes; j++)
|
|
// even if an empty mesh, it's a mesh...
|
|
isMesh[objects[i].startMeshIndex+j]=true;
|
|
}
|
|
for (i=0; i<numMeshes; i++)
|
|
{
|
|
TSMesh * mesh = NULL;
|
|
// decal mesh deprecated
|
|
if (isMesh[i])
|
|
mesh = meshes[i];
|
|
tsalloc.set32( (mesh && mesh->getMeshType() != TSMesh::DecalMeshType) ? mesh->getMeshType() : TSMesh::NullMeshType);
|
|
if (mesh)
|
|
mesh->disassemble();
|
|
}
|
|
delete [] isMesh;
|
|
tsalloc.setGuard();
|
|
|
|
// names
|
|
for (i=0; i<numNames; i++)
|
|
tsalloc.copyToBuffer8((S8 *)(names[i].c_str()),names[i].length()+1);
|
|
|
|
tsalloc.setGuard();
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// write whole shape
|
|
//-------------------------------------------------
|
|
/** Determine whether we can write this shape in TSTPRO compatible format */
|
|
bool TSShape::canWriteOldFormat() const
|
|
{
|
|
// Cannot use old format if using autobillboard details
|
|
for (S32 i = 0; i < details.size(); i++)
|
|
{
|
|
if (details[i].subShapeNum < 0)
|
|
return false;
|
|
}
|
|
|
|
for (S32 i = 0; i < meshes.size(); i++)
|
|
{
|
|
if (!meshes[i])
|
|
continue;
|
|
|
|
// Cannot use old format if using the new functionality (COLORs, 2nd UV set)
|
|
if (meshes[i]->mTverts2.size() || meshes[i]->mColors.size())
|
|
return false;
|
|
|
|
// Cannot use old format if any primitive has too many triangles
|
|
// (ie. cannot fit in a S16)
|
|
for (S32 j = 0; j < meshes[i]->mPrimitives.size(); j++)
|
|
{
|
|
if ((meshes[i]->mPrimitives[j].start +
|
|
meshes[i]->mPrimitives[j].numElements) >= (1 << 15))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TSShape::write(Stream * s, bool saveOldFormat)
|
|
{
|
|
S32 currentVersion = smVersion;
|
|
if (saveOldFormat)
|
|
smVersion = 24;
|
|
|
|
// write version
|
|
s->write(smVersion | (mExporterVersion<<16));
|
|
|
|
tsalloc.setWrite();
|
|
disassembleShape();
|
|
|
|
S32 * buffer32 = tsalloc.getBuffer32();
|
|
S16 * buffer16 = tsalloc.getBuffer16();
|
|
S8 * buffer8 = tsalloc.getBuffer8();
|
|
|
|
S32 size32 = tsalloc.getBufferSize32();
|
|
S32 size16 = tsalloc.getBufferSize16();
|
|
S32 size8 = tsalloc.getBufferSize8();
|
|
|
|
// convert sizes to dwords...
|
|
if (size16 & 1)
|
|
size16 += 2;
|
|
size16 >>= 1;
|
|
if (size8 & 3)
|
|
size8 += 4;
|
|
size8 >>= 2;
|
|
|
|
if (smVersion >= 29)
|
|
{
|
|
const U32 KEY_LEN = 256;
|
|
U8 key[KEY_LEN];
|
|
compressionKey(key, KEY_LEN);
|
|
|
|
U32 offset = 0;
|
|
xorBufferAtOffset(buffer32, size32 * 4, offset, key, KEY_LEN);
|
|
offset += size32 * 4;
|
|
xorBufferAtOffset(buffer16, size16 * 4, offset, key, KEY_LEN);
|
|
offset += size16 * 4;
|
|
xorBufferAtOffset(buffer8, size8 * 4, offset, key, KEY_LEN);
|
|
}
|
|
|
|
S32 sizeMemBuffer, start16, start8;
|
|
sizeMemBuffer = size32 + size16 + size8;
|
|
start16 = size32;
|
|
start8 = start16+size16;
|
|
|
|
// in dwords -- write will properly endian-flip.
|
|
s->write(sizeMemBuffer);
|
|
s->write(start16);
|
|
s->write(start8);
|
|
|
|
// endian-flip the entire write buffers.
|
|
fixEndian(buffer32,buffer16,buffer8,size32,size16,size8);
|
|
|
|
// now write buffers
|
|
s->write(size32*4,buffer32);
|
|
s->write(size16*4,buffer16);
|
|
s->write(size8 *4,buffer8);
|
|
|
|
// write sequences - write will properly endian-flip.
|
|
s->write(sequences.size());
|
|
for (S32 i=0; i<sequences.size(); i++)
|
|
sequences[i].write(s);
|
|
|
|
// write material list - write will properly endian-flip.
|
|
materialList->write(*s);
|
|
|
|
delete [] buffer32;
|
|
delete [] buffer16;
|
|
delete [] buffer8;
|
|
|
|
smVersion = currentVersion;
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// read whole shape
|
|
//-------------------------------------------------
|
|
|
|
bool TSShape::read(Stream * s)
|
|
{
|
|
// read version - read handles endian-flip
|
|
s->read(&smReadVersion);
|
|
mExporterVersion = smReadVersion >> 16;
|
|
smReadVersion &= 0xFF;
|
|
if (smReadVersion>smVersion)
|
|
{
|
|
// error -- don't support future versions yet :>
|
|
Con::errorf(ConsoleLogEntry::General,
|
|
"Error: attempt to load a version %i dts-shape, can currently only load version %i and before.",
|
|
smReadVersion,smVersion);
|
|
return false;
|
|
}
|
|
mReadVersion = smReadVersion;
|
|
|
|
S32 * memBuffer32;
|
|
S16 * memBuffer16;
|
|
S8 * memBuffer8;
|
|
S32 count32, count16, count8;
|
|
if (mReadVersion<19)
|
|
{
|
|
Con::errorf("... Shape with old version.");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
S32 i;
|
|
U32 sizeMemBuffer, startU16, startU8;
|
|
|
|
// in dwords. - read handles endian-flip
|
|
s->read(&sizeMemBuffer);
|
|
s->read(&startU16);
|
|
s->read(&startU8);
|
|
|
|
if (s->getStatus()!=Stream::Ok)
|
|
{
|
|
Con::errorf(ConsoleLogEntry::General, "Error: bad shape file.");
|
|
return false;
|
|
}
|
|
|
|
S32 * tmp = new S32[sizeMemBuffer];
|
|
s->read(sizeof(S32) * sizeMemBuffer, (U8*)tmp);
|
|
|
|
if (smReadVersion >= 29)
|
|
{
|
|
const U32 KEY_LEN = 256;
|
|
U8 key[KEY_LEN];
|
|
compressionKey(key, KEY_LEN);
|
|
|
|
// The whole tmp block is one contiguous encrypted region
|
|
xorBufferAtOffset(tmp, sizeMemBuffer * 4, 0, key, KEY_LEN);
|
|
}
|
|
|
|
memBuffer32 = tmp;
|
|
memBuffer16 = (S16*)(tmp+startU16);
|
|
memBuffer8 = (S8*)(tmp+startU8);
|
|
|
|
count32 = startU16;
|
|
count16 = startU8-startU16;
|
|
count8 = sizeMemBuffer-startU8;
|
|
|
|
// read sequences
|
|
S32 numSequences;
|
|
s->read(&numSequences);
|
|
sequences.setSize(numSequences);
|
|
for (i=0; i<numSequences; i++)
|
|
{
|
|
sequences[i].read(s);
|
|
|
|
// Store initial (empty) source data
|
|
sequences[i].sourceData.total = sequences[i].numKeyframes;
|
|
sequences[i].sourceData.end = sequences[i].sourceData.total - 1;
|
|
}
|
|
|
|
// read material list
|
|
delete materialList; // just in case...
|
|
materialList = new TSMaterialList;
|
|
materialList->read(*s);
|
|
}
|
|
|
|
// since we read in the buffers, we need to endian-flip their entire contents...
|
|
fixEndian(memBuffer32,memBuffer16,memBuffer8,count32,count16,count8);
|
|
|
|
tsalloc.setRead(memBuffer32,memBuffer16,memBuffer8,true);
|
|
assembleShape(); // determine size of buffer needed
|
|
mShapeDataSize = tsalloc.getSize();
|
|
tsalloc.doAlloc();
|
|
mShapeData = tsalloc.getBuffer();
|
|
tsalloc.setRead(memBuffer32,memBuffer16,memBuffer8,false);
|
|
assembleShape(); // copy to buffer
|
|
AssertFatal(tsalloc.getSize()==mShapeDataSize,"TSShape::read: shape data buffer size mis-calculated");
|
|
|
|
delete [] memBuffer32;
|
|
|
|
if (smInitOnRead)
|
|
{
|
|
init();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TSShape::createEmptyShape()
|
|
{
|
|
nodes.set(dMalloc(1 * sizeof(Node)), 1);
|
|
nodes[0].nameIndex = 1;
|
|
nodes[0].parentIndex = -1;
|
|
nodes[0].firstObject = 0;
|
|
nodes[0].firstChild = -1;
|
|
nodes[0].nextSibling = -1;
|
|
|
|
objects.set(dMalloc(1 * sizeof(Object)), 1);
|
|
objects[0].nameIndex = 2;
|
|
objects[0].numMeshes = 1;
|
|
objects[0].startMeshIndex = 0;
|
|
objects[0].nodeIndex = 0;
|
|
objects[0].nextSibling = -1;
|
|
objects[0].firstDecal = -1;
|
|
|
|
objectStates.set(dMalloc(1 * sizeof(ObjectState)), 1);
|
|
objectStates[0].vis = 1;
|
|
objectStates[0].frameIndex = 0;
|
|
objectStates[0].matFrameIndex = 0;
|
|
|
|
subShapeFirstNode.set(dMalloc(1 * sizeof(S32)), 1);
|
|
subShapeFirstNode[0] = 0;
|
|
|
|
subShapeFirstObject.set(dMalloc(1 * sizeof(S32)), 1);
|
|
subShapeFirstObject[0] = 0;
|
|
|
|
detailFirstSkin.set(NULL, 0);
|
|
|
|
subShapeNumNodes.set(dMalloc(1 * sizeof(S32)), 1);
|
|
subShapeNumNodes[0] = 1;
|
|
|
|
subShapeNumObjects.set(dMalloc(1 * sizeof(S32)), 1);
|
|
subShapeNumObjects[0] = 1;
|
|
|
|
details.set(dMalloc(1 * sizeof(Detail)), 1);
|
|
details[0].nameIndex = 0;
|
|
details[0].subShapeNum = 0;
|
|
details[0].objectDetailNum = 0;
|
|
details[0].size = 2.0f;
|
|
details[0].averageError = -1.0f;
|
|
details[0].maxError = -1.0f;
|
|
details[0].polyCount = 0;
|
|
|
|
defaultRotations.set(dMalloc(1 * sizeof(Quat16)), 1);
|
|
defaultRotations[0].x = 0.0f;
|
|
defaultRotations[0].y = 0.0f;
|
|
defaultRotations[0].z = 0.0f;
|
|
defaultRotations[0].w = 0.0f;
|
|
|
|
defaultTranslations.set(dMalloc(1 * sizeof(Point3F)), 1);
|
|
defaultTranslations[0].set(0.0f, 0.0f, 0.0f);
|
|
|
|
subShapeFirstTranslucentObject.set(dMalloc(1 * sizeof(S32)), 1);
|
|
subShapeFirstTranslucentObject[0] = 1;
|
|
|
|
alphaIn.set(dMalloc(1 * sizeof(F32)), 1);
|
|
alphaIn[0] = 0;
|
|
|
|
alphaOut.set(dMalloc(1 * sizeof(F32)), 1);
|
|
alphaOut[0] = -1;
|
|
|
|
sequences.set(NULL, 0);
|
|
nodeRotations.set(NULL, 0);
|
|
nodeTranslations.set(NULL, 0);
|
|
nodeUniformScales.set(NULL, 0);
|
|
nodeAlignedScales.set(NULL, 0);
|
|
nodeArbitraryScaleRots.set(NULL, 0);
|
|
nodeArbitraryScaleFactors.set(NULL, 0);
|
|
groundRotations.set(NULL, 0);
|
|
groundTranslations.set(NULL, 0);
|
|
triggers.set(NULL, 0);
|
|
billboardDetails.set(NULL, 0);
|
|
|
|
names.setSize(3);
|
|
names[0] = StringTable->insert("Detail2");
|
|
names[1] = StringTable->insert("Mesh2");
|
|
names[2] = StringTable->insert("Mesh");
|
|
|
|
mRadius = 0.866025f;
|
|
tubeRadius = 0.707107f;
|
|
center.set(0.0f, 0.5f, 0.0f);
|
|
mBounds.minExtents.set(-0.5f, 0.0f, -0.5f);
|
|
mBounds.maxExtents.set(0.5f, 1.0f, 0.5f);
|
|
|
|
mExporterVersion = 124;
|
|
mSmallestVisibleSize = 2;
|
|
mSmallestVisibleDL = 0;
|
|
mReadVersion = 24;
|
|
mFlags = 0;
|
|
mSequencesConstructed = 0;
|
|
|
|
mUseDetailFromScreenError = false;
|
|
|
|
mDetailLevelLookup.setSize( 1 );
|
|
mDetailLevelLookup[0].set( -1, 0 );
|
|
|
|
// Init the collision accelerator array. Note that we don't compute the
|
|
// accelerators until the app requests them
|
|
detailCollisionAccelerators.setSize(details.size());
|
|
for (U32 i = 0; i < detailCollisionAccelerators.size(); i++)
|
|
detailCollisionAccelerators[i] = NULL;
|
|
}
|
|
|
|
void TSShape::fixEndian(S32 * buff32, S16 * buff16, S8 *, S32 count32, S32 count16, S32)
|
|
{
|
|
// if endian-ness isn't the same, need to flip the buffer contents.
|
|
if (0x12345678!=convertLEndianToHost(0x12345678))
|
|
{
|
|
for (S32 i=0; i<count32; i++)
|
|
buff32[i]=convertLEndianToHost(buff32[i]);
|
|
for (S32 i=0; i<count16*2; i++)
|
|
buff16[i]=convertLEndianToHost(buff16[i]);
|
|
}
|
|
}
|
|
|
|
template<> void *Resource<TSShape>::create(const Torque::Path &path)
|
|
{
|
|
// Execute the shape script if it exists
|
|
Torque::Path scriptPath(path);
|
|
scriptPath.setExtension(TORQUE_SCRIPT_EXTENSION);
|
|
|
|
// Don't execute the script if we're already doing so!
|
|
StringTableEntry currentScript = Platform::stripBasePath(Con::getCurrentScriptModulePath());
|
|
if (!scriptPath.getFullPath().equal(currentScript))
|
|
{
|
|
Torque::Path scriptPathDSO(scriptPath);
|
|
scriptPathDSO.setExtension(TORQUE_SCRIPT_EXTENSION ".dso");
|
|
|
|
if (Torque::FS::IsFile(scriptPathDSO) || Torque::FS::IsFile(scriptPath))
|
|
{
|
|
String evalCmd = "exec(\"" + scriptPath + "\");";
|
|
|
|
String instantGroup = Con::getVariable("InstantGroup");
|
|
Con::setIntVariable("InstantGroup", RootGroupId);
|
|
Con::evaluate((const char*)evalCmd.c_str(), false, scriptPath.getFullPath());
|
|
Con::setVariable("InstantGroup", instantGroup.c_str());
|
|
}
|
|
}
|
|
|
|
// Attempt to load the shape
|
|
TSShape * ret = 0;
|
|
bool readSuccess = false;
|
|
const String extension = path.getExtension();
|
|
bool canLoadCached = false;
|
|
|
|
// 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)))
|
|
{
|
|
// Non DTS not found, or cached DTS is newer
|
|
canLoadCached = true;
|
|
}
|
|
}
|
|
|
|
//assume the dts is good since it was zipped on purpose
|
|
Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(cachedPath);
|
|
if (ref && !String::compare("Zip", ref->getTypeStr().c_str()))
|
|
{
|
|
bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false);
|
|
|
|
if (!forceLoadDAE && Torque::FS::IsFile(cachedPath))
|
|
canLoadCached = true;
|
|
}
|
|
|
|
if (extension.equal("dts", String::NoCase) || canLoadCached)
|
|
{
|
|
FileStream stream;
|
|
stream.open(canLoadCached ? cachedPath.getFullPath() : path.getFullPath(), Torque::FS::File::Read);
|
|
if ( stream.getStatus() != Stream::Ok )
|
|
{
|
|
Con::errorf( "Resource<TSShape>::create - Could not open '%s'", path.getFullPath().c_str() );
|
|
return NULL;
|
|
}
|
|
|
|
ret = new TSShape;
|
|
readSuccess = ret->read(&stream);
|
|
}
|
|
else
|
|
{
|
|
const TSShape::ShapeRegistration* regInfo = TSShape::sFindRegInfo(extension);
|
|
if (regInfo == NULL)
|
|
{
|
|
readSuccess = false;
|
|
}
|
|
else
|
|
{
|
|
readSuccess = regInfo->readFunc(path, ret);
|
|
}
|
|
}
|
|
|
|
if( !readSuccess )
|
|
{
|
|
Con::errorf( "Resource<TSShape>::create - Error reading '%s'", path.getFullPath().c_str() );
|
|
delete ret;
|
|
ret = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
template<> ResourceBase::Signature Resource<TSShape>::signature()
|
|
{
|
|
return MakeFourCC('t','s','s','h');
|
|
}
|
|
|
|
TSShape::ConvexHullAccelerator* TSShape::getAccelerator(S32 dl)
|
|
{
|
|
AssertFatal(dl < details.size(), "Error, bad detail level!");
|
|
if (dl == -1)
|
|
return NULL;
|
|
|
|
AssertFatal( detailCollisionAccelerators.size() == details.size(),
|
|
"TSShape::getAccelerator() - mismatched array sizes!" );
|
|
|
|
if (detailCollisionAccelerators[dl] == NULL)
|
|
computeAccelerator(dl);
|
|
|
|
AssertFatal(detailCollisionAccelerators[dl] != NULL, "This should be non-null after computing it!");
|
|
return detailCollisionAccelerators[dl];
|
|
}
|
|
|
|
|
|
void TSShape::computeAccelerator(S32 dl)
|
|
{
|
|
AssertFatal(dl < details.size(), "Error, bad detail level!");
|
|
|
|
// Have we already computed this?
|
|
if (detailCollisionAccelerators[dl] != NULL)
|
|
return;
|
|
|
|
// Create a bogus features list...
|
|
ConvexFeature cf;
|
|
MatrixF mat(true);
|
|
Point3F n(0, 0, 1);
|
|
|
|
const TSDetail* detail = &details[dl];
|
|
S32 ss = detail->subShapeNum;
|
|
S32 od = detail->objectDetailNum;
|
|
|
|
S32 start = subShapeFirstObject[ss];
|
|
S32 end = subShapeNumObjects[ss] + start;
|
|
if (start < end)
|
|
{
|
|
// run through objects and collide
|
|
// DMMNOTE: This assumes that the transform of the collision hulls is
|
|
// identity...
|
|
U32 surfaceKey = 0;
|
|
for (S32 i = start; i < end; i++)
|
|
{
|
|
const TSObject* obj = &objects[i];
|
|
|
|
if (obj->numMeshes && od < obj->numMeshes) {
|
|
TSMesh* mesh = meshes[obj->startMeshIndex + od];
|
|
if (mesh)
|
|
mesh->getFeatures(0, mat, n, &cf, surfaceKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector<Point3F> fixedVerts;
|
|
VECTOR_SET_ASSOCIATION(fixedVerts);
|
|
S32 i;
|
|
for (i = 0; i < cf.mVertexList.size(); i++) {
|
|
S32 j;
|
|
bool found = false;
|
|
for (j = 0; j < cf.mFaceList.size(); j++) {
|
|
if (cf.mFaceList[j].vertex[0] == i ||
|
|
cf.mFaceList[j].vertex[1] == i ||
|
|
cf.mFaceList[j].vertex[2] == i) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
continue;
|
|
|
|
found = false;
|
|
for (j = 0; j < fixedVerts.size(); j++) {
|
|
if (fixedVerts[j] == cf.mVertexList[i]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found == true) {
|
|
// Ok, need to replace any references to vertex i in the facelists with
|
|
// a reference to vertex j in the fixed list
|
|
for (S32 k = 0; k < cf.mFaceList.size(); k++) {
|
|
for (S32 l = 0; l < 3; l++) {
|
|
if (cf.mFaceList[k].vertex[l] == i)
|
|
cf.mFaceList[k].vertex[l] = j;
|
|
}
|
|
}
|
|
} else {
|
|
for (S32 k = 0; k < cf.mFaceList.size(); k++) {
|
|
for (S32 l = 0; l < 3; l++) {
|
|
if (cf.mFaceList[k].vertex[l] == i)
|
|
cf.mFaceList[k].vertex[l] = fixedVerts.size();
|
|
}
|
|
}
|
|
fixedVerts.push_back(cf.mVertexList[i]);
|
|
}
|
|
}
|
|
cf.mVertexList.setSize(0);
|
|
cf.mVertexList = fixedVerts;
|
|
|
|
// Ok, so now we have a vertex list. Lets copy that out...
|
|
ConvexHullAccelerator* accel = new ConvexHullAccelerator;
|
|
detailCollisionAccelerators[dl] = accel;
|
|
accel->numVerts = cf.mVertexList.size();
|
|
accel->vertexList = new Point3F[accel->numVerts];
|
|
dMemcpy(accel->vertexList, cf.mVertexList.address(), sizeof(Point3F) * accel->numVerts);
|
|
|
|
accel->normalList = new Point3F[cf.mFaceList.size()];
|
|
for (i = 0; i < cf.mFaceList.size(); i++)
|
|
accel->normalList[i] = cf.mFaceList[i].normal;
|
|
|
|
accel->emitStrings = new U8*[accel->numVerts];
|
|
dMemset(accel->emitStrings, 0, sizeof(U8*) * accel->numVerts);
|
|
|
|
for (i = 0; i < accel->numVerts; i++) {
|
|
S32 j;
|
|
|
|
Vector<U32> faces;
|
|
VECTOR_SET_ASSOCIATION(faces);
|
|
for (j = 0; j < cf.mFaceList.size(); j++) {
|
|
if (cf.mFaceList[j].vertex[0] == i ||
|
|
cf.mFaceList[j].vertex[1] == i ||
|
|
cf.mFaceList[j].vertex[2] == i) {
|
|
faces.push_back(j);
|
|
}
|
|
}
|
|
AssertFatal(faces.size() != 0, "Huh? Vertex unreferenced by any faces");
|
|
|
|
// Insert all faces that didn't make the first cut, but share a plane with
|
|
// a face that's on the short list.
|
|
for (j = 0; j < cf.mFaceList.size(); j++) {
|
|
bool found = false;
|
|
S32 k;
|
|
for (k = 0; k < faces.size(); k++) {
|
|
if (faces[k] == j)
|
|
found = true;
|
|
}
|
|
if (found)
|
|
continue;
|
|
|
|
found = false;
|
|
for (k = 0; k < faces.size(); k++) {
|
|
if (mDot(accel->normalList[faces[k]], accel->normalList[j]) > 0.999) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found)
|
|
faces.push_back(j);
|
|
}
|
|
|
|
Vector<U32> vertRemaps;
|
|
VECTOR_SET_ASSOCIATION(vertRemaps);
|
|
for (j = 0; j < faces.size(); j++) {
|
|
for (U32 k = 0; k < 3; k++) {
|
|
U32 insert = cf.mFaceList[faces[j]].vertex[k];
|
|
bool found = false;
|
|
for (S32 l = 0; l < vertRemaps.size(); l++) {
|
|
if (insert == vertRemaps[l]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
vertRemaps.push_back(insert);
|
|
}
|
|
}
|
|
|
|
Vector<Point2I> edges;
|
|
VECTOR_SET_ASSOCIATION(edges);
|
|
for (j = 0; j < faces.size(); j++) {
|
|
for (U32 k = 0; k < 3; k++) {
|
|
U32 edgeStart = cf.mFaceList[faces[j]].vertex[(k + 0) % 3];
|
|
U32 edgeEnd = cf.mFaceList[faces[j]].vertex[(k + 1) % 3];
|
|
|
|
U32 e0 = getMin(edgeStart, edgeEnd);
|
|
U32 e1 = getMax(edgeStart, edgeEnd);
|
|
|
|
bool found = false;
|
|
for (S32 l = 0; l < edges.size(); l++) {
|
|
if (edges[l].x == e0 && edges[l].y == e1) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
edges.push_back(Point2I(e0, e1));
|
|
}
|
|
}
|
|
|
|
//AssertFatal(vertRemaps.size() < 256 && faces.size() < 256 && edges.size() < 256,
|
|
// "Error, ran over the shapebase assumptions about convex hulls.");
|
|
|
|
U32 emitStringLen = 1 + vertRemaps.size() +
|
|
1 + (edges.size() * 2) +
|
|
1 + (faces.size() * 4);
|
|
accel->emitStrings[i] = new U8[emitStringLen];
|
|
|
|
U32 currPos = 0;
|
|
|
|
accel->emitStrings[i][currPos++] = vertRemaps.size();
|
|
for (j = 0; j < vertRemaps.size(); j++)
|
|
accel->emitStrings[i][currPos++] = vertRemaps[j];
|
|
|
|
accel->emitStrings[i][currPos++] = edges.size();
|
|
for (j = 0; j < edges.size(); j++) {
|
|
S32 l;
|
|
U32 old = edges[j].x;
|
|
bool found = false;
|
|
for (l = 0; l < vertRemaps.size(); l++) {
|
|
if (vertRemaps[l] == old) {
|
|
found = true;
|
|
accel->emitStrings[i][currPos++] = l;
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(found, "Error, couldn't find the remap!");
|
|
|
|
old = edges[j].y;
|
|
found = false;
|
|
for (l = 0; l < vertRemaps.size(); l++) {
|
|
if (vertRemaps[l] == old) {
|
|
found = true;
|
|
accel->emitStrings[i][currPos++] = l;
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(found, "Error, couldn't find the remap!");
|
|
}
|
|
|
|
accel->emitStrings[i][currPos++] = faces.size();
|
|
for (j = 0; j < faces.size(); j++) {
|
|
accel->emitStrings[i][currPos++] = faces[j];
|
|
for (U32 k = 0; k < 3; k++) {
|
|
U32 old = cf.mFaceList[faces[j]].vertex[k];
|
|
bool found = false;
|
|
for (S32 l = 0; l < vertRemaps.size(); l++) {
|
|
if (vertRemaps[l] == old) {
|
|
found = true;
|
|
accel->emitStrings[i][currPos++] = l;
|
|
break;
|
|
}
|
|
}
|
|
AssertFatal(found, "Error, couldn't find the remap!");
|
|
}
|
|
}
|
|
AssertFatal(currPos == emitStringLen, "Error, over/underflowed the emission string!");
|
|
}
|
|
}
|
|
|
|
void TSShape::finalizeEditable()
|
|
{
|
|
for (U32 i = 0; i < meshes.size(); i++)
|
|
{
|
|
if (meshes[i])
|
|
{
|
|
meshes[i]->clearEditable();
|
|
}
|
|
}
|
|
}
|