mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-29 13:25:52 +01:00
430e0c8a95
.. but is really fine (most probably). I added comments instead; see them for explanation why it's fine ;-)
3100 lines
97 KiB
C++
3100 lines
97 KiB
C++
/* Copyright (c) 2002-2012 Croteam Ltd.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of version 2 of the GNU General Public License as published by
|
|
the Free Software Foundation
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
|
|
|
|
#include "Engine/StdH.h"
|
|
|
|
#include <Engine/Models/ModelObject.h>
|
|
#include <Engine/Models/ModelData.h>
|
|
#include <Engine/Models/RenderModel.h>
|
|
#include <Engine/Models/Model_internal.h>
|
|
#include <Engine/Models/Normals.h>
|
|
#include <Engine/Base/Stream.h>
|
|
#include <Engine/Base/CTString.inl>
|
|
#include <Engine/Math/Clipping.inl>
|
|
#include <Engine/Graphics/Color.h>
|
|
#include <Engine/Graphics/DrawPort.h>
|
|
|
|
#include <Engine/Templates/StaticArray.cpp>
|
|
#include <Engine/Templates/DynamicArray.cpp>
|
|
#include <Engine/Templates/DynamicContainer.cpp>
|
|
|
|
#include <Engine/Templates/Stock_CModelData.h>
|
|
|
|
template class CStaticArray<MappingSurface>;
|
|
template class CStaticArray<ModelPolygon>;
|
|
template class CStaticArray<ModelPolygonVertex>;
|
|
template class CStaticArray<ModelTextureVertex>;
|
|
template class CStaticArray<PolygonsPerPatch>;
|
|
template class CDynamicArray<CAttachedModelPosition>;
|
|
|
|
|
|
extern UBYTE aubGouraudConv[16384];
|
|
|
|
// model LOD biasing control
|
|
extern FLOAT mdl_fLODMul;
|
|
extern FLOAT mdl_fLODAdd;
|
|
extern INDEX mdl_iLODDisappear; // 0=never, 1=ignore bias, 2=with bias
|
|
extern INDEX mdl_bFineQuality; // 0=force to 8-bit, 1=optimal
|
|
|
|
|
|
|
|
CModelData::CModelData(const CModelData &c) { ASSERT(FALSE); };
|
|
CModelData &CModelData::operator=(const CModelData &c){ ASSERT(FALSE); return *this; };
|
|
|
|
// if any surface in model that we are currently reading has any transparency
|
|
BOOL _bHasAlpha;
|
|
|
|
// colors used to represent on and off bits
|
|
COLOR PaletteColorValues[] =
|
|
{
|
|
C_RED, C_GREEN, C_BLUE, C_CYAN,
|
|
C_MAGENTA, C_YELLOW, C_ORANGE, C_BROWN,
|
|
C_PINK, C_dGRAY, C_GRAY, C_lGRAY,
|
|
C_dRED, C_lRED, C_dGREEN, C_lGREEN,
|
|
C_dBLUE, C_lBLUE, C_dCYAN, C_lCYAN,
|
|
C_dMAGENTA, C_lMAGENTA, C_dYELLOW, C_lYELLOW,
|
|
C_dORANGE, C_lORANGE, C_dBROWN, C_lBROWN,
|
|
C_dPINK, C_lPINK, C_WHITE, C_BLACK,
|
|
};
|
|
|
|
/*
|
|
* Instanciated global rendering preferences object containing
|
|
* info about rendering of models
|
|
*/
|
|
CModelRenderPrefs _mrpModelRenderPrefs;
|
|
|
|
/*
|
|
* Functions dealing with 16-bit normal compression
|
|
*/
|
|
void CompressNormal_HQ(const FLOAT3D &vNormal, UBYTE &ubH, UBYTE &ubP)
|
|
{
|
|
ANGLE h, p;
|
|
|
|
const FLOAT &x = vNormal(1);
|
|
const FLOAT &y = vNormal(2);
|
|
const FLOAT &z = vNormal(3);
|
|
|
|
// calculate pitch
|
|
p = ASin(y);
|
|
|
|
// if y is near +1 or -1
|
|
if (y>0.99 || y<-0.99) {
|
|
// heading is irrelevant
|
|
h = 0;
|
|
// otherwise
|
|
} else {
|
|
// calculate heading
|
|
h = ATan2(-x, -z);
|
|
}
|
|
|
|
h = (h/360.0f)+0.5f;
|
|
p = (p/360.0f)+0.5f;
|
|
ASSERT(h>=0 && h<=1);
|
|
ASSERT(p>=0 && p<=1);
|
|
ubH = UBYTE(h*255);
|
|
ubP = UBYTE(p*255);
|
|
}
|
|
|
|
void DecompressNormal_HQ(FLOAT3D &vNormal, UBYTE ubH, UBYTE ubP)
|
|
{
|
|
ANGLE h = (ubH/255.0f)*360.0f-180.0f;
|
|
ANGLE p = (ubP/255.0f)*180.0f-90.0f;
|
|
|
|
FLOAT &x = vNormal(1);
|
|
FLOAT &y = vNormal(2);
|
|
FLOAT &z = vNormal(3);
|
|
|
|
x = -Sin(h)*Cos(p);
|
|
y = Sin(p);
|
|
z = -Cos(h)*Cos(p);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Function returns number of first setted bit in ULONG
|
|
*/
|
|
INDEX GetBit( ULONG ulSource)
|
|
{
|
|
for( INDEX i=0; i<32; i++)
|
|
{
|
|
if( (ulSource & (1<<i)) != 0) return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Default constructor sets default rendering preferences
|
|
*/
|
|
CModelRenderPrefs::CModelRenderPrefs()
|
|
{
|
|
rp_BBoxFrameVisible = FALSE;
|
|
rp_BBoxAllVisible = FALSE;
|
|
rp_InkColor = C_BLACK;
|
|
rp_PaperColor = C_lGRAY;
|
|
rp_RenderType = RT_TEXTURE | RT_SHADING_PHONG;
|
|
rp_ShadowQuality = 0;
|
|
}
|
|
/*
|
|
* Routines managing (get/set) rendering preferences
|
|
*/
|
|
void CModelRenderPrefs::SetRenderType(ULONG rtNew)
|
|
{
|
|
rp_RenderType = rtNew;
|
|
}
|
|
void CModelRenderPrefs::SetTextureType(ULONG rtNew)
|
|
{
|
|
rp_RenderType = (rp_RenderType & (~RT_TEXTURE_MASK)) | rtNew;
|
|
}
|
|
void CModelRenderPrefs::SetShadingType(ULONG rtNew)
|
|
{
|
|
rp_RenderType = (rp_RenderType & (~RT_SHADING_MASK)) | rtNew;
|
|
}
|
|
void CModelRenderPrefs::SetWire(BOOL bWireOn)
|
|
{
|
|
if( bWireOn)
|
|
{
|
|
rp_RenderType = rp_RenderType | RT_WIRE_ON;
|
|
}
|
|
else
|
|
{
|
|
rp_RenderType = rp_RenderType & (~RT_WIRE_ON);
|
|
}
|
|
}
|
|
void CModelRenderPrefs::SetHiddenLines(BOOL bHiddenLinesOn)
|
|
{
|
|
if( bHiddenLinesOn)
|
|
{
|
|
rp_RenderType = rp_RenderType | RT_HIDDEN_LINES;
|
|
}
|
|
else
|
|
{
|
|
rp_RenderType = rp_RenderType & (~RT_HIDDEN_LINES);
|
|
}
|
|
}
|
|
ULONG CModelRenderPrefs::GetRenderType()
|
|
{
|
|
return( rp_RenderType);
|
|
}
|
|
void CModelRenderPrefs::SetShadowQuality(INDEX iNewQuality)
|
|
{
|
|
ASSERT( iNewQuality >= 0);
|
|
rp_ShadowQuality = iNewQuality;
|
|
}
|
|
void CModelRenderPrefs::DesreaseShadowQuality(void)
|
|
{
|
|
rp_ShadowQuality += 1;
|
|
}
|
|
void CModelRenderPrefs::IncreaseShadowQuality(void)
|
|
{
|
|
if( rp_ShadowQuality > 0)
|
|
rp_ShadowQuality -= 1;
|
|
}
|
|
INDEX CModelRenderPrefs::GetShadowQuality()
|
|
{
|
|
return( rp_ShadowQuality);
|
|
}
|
|
BOOL CModelRenderPrefs::BBoxFrameVisible()
|
|
{
|
|
return( rp_BBoxFrameVisible);
|
|
}
|
|
void CModelRenderPrefs::BBoxFrameShow( BOOL bShow)
|
|
{
|
|
rp_BBoxFrameVisible = bShow;
|
|
}
|
|
BOOL CModelRenderPrefs::BBoxAllVisible()
|
|
{
|
|
return( rp_BBoxAllVisible);
|
|
}
|
|
void CModelRenderPrefs::BBoxAllShow( BOOL bShow)
|
|
{
|
|
rp_BBoxAllVisible = bShow;
|
|
}
|
|
BOOL CModelRenderPrefs::WireOn()
|
|
{
|
|
return( (rp_RenderType & RT_WIRE_ON) != 0);
|
|
}
|
|
BOOL CModelRenderPrefs::HiddenLines()
|
|
{
|
|
return( (rp_RenderType & RT_HIDDEN_LINES) != 0);
|
|
}
|
|
void CModelRenderPrefs::SetInkColor(COLOR clrNew)
|
|
{
|
|
rp_InkColor = clrNew;
|
|
}
|
|
COLOR CModelRenderPrefs::GetInkColor()
|
|
{
|
|
return rp_InkColor;
|
|
}
|
|
void CModelRenderPrefs::SetPaperColor(COLOR clrNew)
|
|
{
|
|
rp_PaperColor = clrNew;
|
|
}
|
|
COLOR CModelRenderPrefs::GetPaperColor()
|
|
{
|
|
return rp_PaperColor;
|
|
}
|
|
// read and write functions
|
|
void CModelRenderPrefs::Read_t( CTStream *istrFile) // throw char *
|
|
{
|
|
}
|
|
void CModelRenderPrefs::Write_t( CTStream *ostrFile) // throw char *
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
CModelPatch::CModelPatch(void)
|
|
{
|
|
mp_strName = "";
|
|
mp_mexPosition = MEX2D( 1024, 1024);
|
|
mp_fStretch = 1.0f;
|
|
}
|
|
|
|
void CModelPatch::Read_t(CTStream *strFile)
|
|
{
|
|
*strFile >> mp_strName;
|
|
CTFileName fnPatchTexture;
|
|
*strFile >> fnPatchTexture;
|
|
try
|
|
{
|
|
mp_toTexture.SetData_t( fnPatchTexture);
|
|
}
|
|
catch( char *strError)
|
|
{
|
|
(void) strError;
|
|
}
|
|
*strFile >> mp_mexPosition;
|
|
*strFile >> mp_fStretch;
|
|
}
|
|
|
|
void CModelPatch::Write_t(CTStream *strFile)
|
|
{
|
|
*strFile << mp_strName;
|
|
*strFile << mp_toTexture.GetName();
|
|
*strFile << mp_mexPosition;
|
|
*strFile << mp_fStretch;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Default constructor sets invalid data
|
|
*/
|
|
CModelData::CModelData()
|
|
{
|
|
INDEX i;
|
|
md_bPreparedForRendering = FALSE;
|
|
|
|
md_VerticesCt = 0; // number of vertices in model
|
|
md_FramesCt = 0; // number of all frames used by this model
|
|
md_MipCt = 0; // number of mip-models
|
|
|
|
md_bIsEdited = FALSE; // not edited by default
|
|
|
|
// invalidate mip-model info data
|
|
for( i=0; i<MAX_MODELMIPS; i++) {
|
|
md_MipInfos[i].mmpi_PolygonsCt = 0;
|
|
}
|
|
|
|
md_Flags = 0; // model flags (flat, reflection mapping)
|
|
md_ShadowQuality = 0;
|
|
md_Stretch = FLOAT3D(1,1,1); // stretch vector (static one, dynamic one is in model object)
|
|
md_bCollideAsCube = FALSE; // collide as sphere
|
|
md_colDiffuse = C_WHITE|CT_OPAQUE;
|
|
md_colReflections = C_WHITE|CT_OPAQUE;
|
|
md_colSpecular = C_WHITE|CT_OPAQUE;
|
|
md_colBump = C_WHITE|CT_OPAQUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Clear all model data arrays
|
|
*/
|
|
void CModelData::Clear(void)
|
|
{
|
|
INDEX i;
|
|
md_bPreparedForRendering = FALSE;
|
|
|
|
CAnimData::Clear();
|
|
|
|
md_FrameVertices16.Clear();
|
|
md_FrameVertices8.Clear();
|
|
md_FrameInfos.Clear();
|
|
md_MainMipVertices.Clear();
|
|
md_TransformedVertices.Clear();
|
|
md_VertexMipMask.Clear();
|
|
md_aampAttachedPosition.Clear();
|
|
md_acbCollisionBox.Clear();
|
|
|
|
for( i=0; i<md_MipCt; i++) md_MipInfos[i].Clear();
|
|
for( i=0; i<MAX_COLOR_NAMES; i++) md_ColorNames[i].Clear();
|
|
for( i=0; i<MAX_TEXTUREPATCHES; i++) md_mpPatches[i].mp_toTexture.SetData_t( CTString(""));
|
|
|
|
md_VerticesCt = 0;
|
|
md_FramesCt = 0;
|
|
md_MipCt = 0;
|
|
}
|
|
|
|
|
|
// get amount of memory used by this object
|
|
SLONG CModelData::GetUsedMemory(void)
|
|
{
|
|
SLONG slUsed = sizeof(*this)+CAnimData::GetUsedMemory()-sizeof(CAnimData);
|
|
|
|
slUsed += md_FrameVertices16.Count()*sizeof(struct ModelFrameVertex16);
|
|
slUsed += md_FrameVertices8.Count()*sizeof(struct ModelFrameVertex8);
|
|
slUsed += md_FrameInfos.Count()*sizeof(struct ModelFrameInfo);
|
|
slUsed += md_MainMipVertices.Count()*sizeof(FLOAT3D);
|
|
slUsed += md_TransformedVertices.Count()*sizeof(struct TransformedVertexData);
|
|
slUsed += md_VertexMipMask.Count()*sizeof(ULONG);
|
|
slUsed += md_acbCollisionBox.Count()*sizeof(CModelCollisionBox);
|
|
slUsed += md_aampAttachedPosition.Count()*sizeof(CAttachedModelPosition);
|
|
|
|
for(INDEX i=0; i<md_MipCt; i++) {
|
|
slUsed += md_MipInfos[i].mmpi_aPolygonsPerPatch.Count()*sizeof(struct PolygonsPerPatch);
|
|
slUsed += md_MipInfos[i].mmpi_Polygons.Count()*sizeof(struct ModelPolygon);
|
|
slUsed += md_MipInfos[i].mmpi_TextureVertices.Count()*sizeof(struct ModelTextureVertex);
|
|
slUsed += md_MipInfos[i].mmpi_MappingSurfaces.Count()*sizeof(struct MappingSurface);
|
|
}
|
|
|
|
return slUsed;
|
|
}
|
|
// check if this kind of objects is auto-freed
|
|
BOOL CModelData::IsAutoFreed(void)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
void CModelData::ClearAnimations(void)
|
|
{
|
|
CAnimData::Clear();
|
|
|
|
md_FrameVertices16.Clear();
|
|
md_FrameVertices8.Clear();
|
|
md_FrameInfos.Clear();
|
|
md_FramesCt = 0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// riches texture dimensions
|
|
void CModelData::GetTextureDimensions( MEX &mexWidth, MEX &mexHeight)
|
|
{
|
|
mexWidth = md_Width;
|
|
mexHeight = md_Height;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// calculates bounding box of all frames
|
|
void CModelData::GetAllFramesBBox( FLOATaabbox3D &MaxBB)
|
|
{
|
|
for( INDEX i=0; i<md_FramesCt; i++)
|
|
{
|
|
MaxBB |= md_FrameInfos[ i].mfi_Box;
|
|
}
|
|
}
|
|
|
|
FLOAT3D CModelData::GetCollisionBoxMin(INDEX iCollisionBox)
|
|
{
|
|
md_acbCollisionBox.Lock();
|
|
INDEX iCollisionBoxClamped = Clamp(iCollisionBox, 0, md_acbCollisionBox.Count()-1);
|
|
FLOAT3D vMin = md_acbCollisionBox[ iCollisionBoxClamped].mcb_vCollisionBoxMin;
|
|
md_acbCollisionBox.Unlock();
|
|
return vMin;
|
|
};
|
|
|
|
FLOAT3D CModelData::GetCollisionBoxMax(INDEX iCollisionBox=0)
|
|
{
|
|
md_acbCollisionBox.Lock();
|
|
INDEX iCollisionBoxClamped = Clamp(iCollisionBox, 0, md_acbCollisionBox.Count()-1);
|
|
FLOAT3D vMax = md_acbCollisionBox[ iCollisionBoxClamped].mcb_vCollisionBoxMax;
|
|
md_acbCollisionBox.Unlock();
|
|
return vMax;
|
|
};
|
|
|
|
// returns HEIGHT_EQ_WIDTH, LENGTH_EQ_WIDTH or LENGTH_EQ_HEIGHT
|
|
INDEX CModelData::GetCollisionBoxDimensionEquality(INDEX iCollisionBox=0)
|
|
{
|
|
md_acbCollisionBox.Lock();
|
|
iCollisionBox = Clamp(iCollisionBox, 0, md_acbCollisionBox.Count()-1);
|
|
INDEX iDimEq = md_acbCollisionBox[ iCollisionBox].mcb_iCollisionBoxDimensionEquality;
|
|
md_acbCollisionBox.Unlock();
|
|
return iDimEq;
|
|
};
|
|
|
|
|
|
ULONG CModelData::GetFlags(void)
|
|
{
|
|
return md_Flags;
|
|
};
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
void CModelData::SpreadMipSwitchFactors( INDEX iFirst, float fStartingFactor)
|
|
{
|
|
// set switch steep to cover all mips until max default range reached
|
|
FLOAT fSteep;
|
|
// if we allready skipped max range or we don't have any more mips
|
|
if( (fStartingFactor > MAX_SWITCH_FACTOR) || ((md_MipCt-iFirst) <= 0) )
|
|
{
|
|
// define next switch factor offset
|
|
fSteep = 1.2f;
|
|
}
|
|
|
|
// else divide factor from starting factor to max switch factor with number of mip
|
|
// models left
|
|
else
|
|
{
|
|
fSteep = (MAX_SWITCH_FACTOR - fStartingFactor) / (md_MipCt-iFirst);
|
|
}
|
|
|
|
// spread mip switch factors for rougher mip models
|
|
for( INDEX i=iFirst; i<md_MipCt; i++)
|
|
{
|
|
md_MipSwitchFactors[ i] = fStartingFactor + (i-iFirst+1)*fSteep;
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Default destructor asserts invalid data and clears valid ones
|
|
*/
|
|
CModelData::~CModelData()
|
|
{
|
|
Clear();
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
ModelTextureVertex::ModelTextureVertex(void)
|
|
{
|
|
mtv_iTransformedVertex = 0;
|
|
mtv_Done = FALSE;
|
|
}
|
|
//------------------------------------------ WRITE
|
|
void ModelPolygonVertex::Write_t( CTStream *pFile) // throw char *
|
|
{
|
|
// DG: this looks like a 64-bit issue, but most probably isn't as PtrToIndices() should have been called before this
|
|
(*pFile) << (INDEX) (size_t) mpv_ptvTransformedVertex;
|
|
(*pFile) << (INDEX) (size_t) mpv_ptvTextureVertex;
|
|
}
|
|
//------------------------------------------ READ
|
|
void ModelPolygonVertex::Read_t( CTStream *pFile) // throw char *
|
|
{
|
|
INDEX itmp;
|
|
|
|
// DG: this looks like a 64-bit issue, but most probably isn't as IndicesToPtrs() should be
|
|
// called afterwards to restore actually valid pointers.
|
|
(*pFile) >> itmp;
|
|
mpv_ptvTransformedVertex = (struct TransformedVertexData *) (size_t) itmp;
|
|
(*pFile) >> itmp;
|
|
mpv_ptvTextureVertex = (ModelTextureVertex *) (size_t) itmp;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
//------------------------------------------ WRITE
|
|
void ModelPolygon::Write_t( CTStream *pFile) // throw char *
|
|
{
|
|
pFile->WriteID_t( CChunkID("MDP2"));
|
|
INDEX ctVertices = mp_PolygonVertices.Count();
|
|
(*pFile) << ctVertices;
|
|
{FOREACHINSTATICARRAY(mp_PolygonVertices, ModelPolygonVertex, it)
|
|
{ it.Current().Write_t( pFile);}}
|
|
(*pFile) << mp_RenderFlags;
|
|
(*pFile) << mp_ColorAndAlpha;
|
|
(*pFile) << mp_Surface;
|
|
};
|
|
//------------------------------------------ READ
|
|
void ModelPolygon::Read_t( CTStream *pFile) // throw char *
|
|
{
|
|
INDEX ctVertices;
|
|
ULONG ulDummy;
|
|
if( pFile->PeekID_t() == CChunkID("MDPL"))
|
|
{
|
|
pFile->ExpectID_t( CChunkID("MDPL"));
|
|
pFile->ReadFullChunk_t( CChunkID("IMPV"), &ctVertices, sizeof(INDEX));
|
|
BYTESWAP(ctVertices);
|
|
mp_PolygonVertices.New( ctVertices);
|
|
|
|
{FOREACHINSTATICARRAY(mp_PolygonVertices, ModelPolygonVertex, it)
|
|
{ it.Current().Read_t( pFile);}}
|
|
(*pFile) >> mp_RenderFlags;
|
|
(*pFile) >> mp_ColorAndAlpha;
|
|
(*pFile) >> mp_Surface;
|
|
(*pFile) >> ulDummy; // ex on color
|
|
(*pFile) >> ulDummy; // ex off color
|
|
}
|
|
else
|
|
{
|
|
pFile->ExpectID_t( CChunkID("MDP2"));
|
|
(*pFile) >> ctVertices;
|
|
mp_PolygonVertices.New( ctVertices);
|
|
|
|
{FOREACHINSTATICARRAY(mp_PolygonVertices, ModelPolygonVertex, it)
|
|
{ it.Current().Read_t( pFile);}}
|
|
(*pFile) >> mp_RenderFlags;
|
|
(*pFile) >> mp_ColorAndAlpha;
|
|
(*pFile) >> mp_Surface;
|
|
}
|
|
};
|
|
//----------------------------------------------------------------------------------
|
|
// TEMPORARY - REMOVE THIS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
// POLYGON RENDER CONSTANTS
|
|
// new_render_types = rendertype (0-7)<<0 + phongstrength(0-7)<<3 + alphatype(0-3)<<6
|
|
// poly render types
|
|
#define PR_PHONGSHADING (0x00<<0) // textures phong shading
|
|
#define PR_LAMBERTSHADING (0x01<<0) // textures lambert shading (no phong/gouraud)
|
|
#define PR_ALPHAGOURAUD (0x02<<0) // alpha gouraud shading
|
|
#define PR_FRONTPROJECTION (0x03<<0) // front projection shading (no rotation, 2D zoom)
|
|
#define PR_SHADOWBLENDING (0x04<<0) // shadow blending (black alpha gouraud)
|
|
#define PR_COLORFILLING (0x05<<0) // flat filling (single color poly, no texture)
|
|
|
|
#define PR_MASK (0x07<<0) // mask for render types
|
|
|
|
// phong strengths (shading types)
|
|
#define PS_MATTE (0x00<<3) // same as the gouraud
|
|
#define PS_SHINY (0x01<<3) // mild shininig
|
|
#define PS_METAL (0x02<<3) // shine as hell
|
|
|
|
#define PS_MASK (0x07<<3) // mask for phong strengths
|
|
|
|
// texture's alpha channel flag
|
|
#define AC_SKIPALPHACHANNEL (0x00<<6) // opaque rendering regardless of alpha channel presence
|
|
#define AC_USEALPHACHANNEL (0x01<<6) // texture's alpha ch. will be taken in consideration
|
|
#define AC_ZEROTRANSPARENCY (0x02<<6) // texture's zero values are transparent (no alpha ch.)
|
|
|
|
#define AC_MASK (0x03<<6) // mask for texture's alpha flag
|
|
|
|
// polygon's control flags
|
|
#define PCF_DOUBLESIDED (0x01<<9) // double sided polygon
|
|
#define PCF_NOSHADING (0x02<<9) // this polygon will not be shaded anyhow (?)
|
|
#define PCF_CLIPPOLYGON (0x04<<9) // polygon is clipped, instead rejected
|
|
#define PCF_REFLECTIONS (0x08<<9) // use reflection mapping
|
|
//----------------------------------------------------------------------------------
|
|
|
|
BOOL MappingSurface::operator==(const MappingSurface &msOther) const {
|
|
return msOther.ms_Name == ms_Name;
|
|
};
|
|
|
|
// convert old polygon flags from CTGfx into new rendering parameters
|
|
void MappingSurface::SetRenderingParameters(ULONG ulOldFlags)
|
|
{
|
|
// find rendering type
|
|
if (ulOldFlags&PCF_NOSHADING) {
|
|
ms_sstShadingType = SST_FULLBRIGHT;
|
|
} else {
|
|
switch (ulOldFlags&PS_MASK) {
|
|
case PS_MATTE:
|
|
ms_sstShadingType = SST_MATTE;
|
|
break;
|
|
case PS_SHINY:
|
|
ms_sstShadingType = SST_MATTE;
|
|
break;
|
|
case PS_METAL:
|
|
ms_sstShadingType = SST_MATTE;
|
|
break;
|
|
default:
|
|
ms_sstShadingType = SST_MATTE;
|
|
}
|
|
}
|
|
// find translucency type
|
|
if ((ulOldFlags&PR_MASK)==PR_ALPHAGOURAUD) {
|
|
ms_sttTranslucencyType = STT_ALPHAGOURAUD;
|
|
} else if ((ulOldFlags&AC_MASK)==AC_ZEROTRANSPARENCY) {
|
|
ms_sttTranslucencyType = STT_TRANSLUCENT;
|
|
} else {
|
|
ms_sttTranslucencyType = STT_OPAQUE;
|
|
}
|
|
// find flags
|
|
ms_ulRenderingFlags = 0;
|
|
if (ulOldFlags&PCF_DOUBLESIDED) {
|
|
ms_ulRenderingFlags|=SRF_DOUBLESIDED;
|
|
}
|
|
if (ulOldFlags&PCF_REFLECTIONS) {
|
|
ms_ulRenderingFlags|=SRF_REFLECTIONS;
|
|
}
|
|
}
|
|
//------------------------------------------ WRITE
|
|
void MappingSurface::Write_t( CTStream *pFile) // throw char *
|
|
{
|
|
(*pFile) << ms_Name;
|
|
pFile->Write_t( &ms_vSurface2DOffset, sizeof(FLOAT3D));
|
|
pFile->Write_t( &ms_HPB, sizeof(FLOAT3D));
|
|
pFile->Write_t( &ms_Zoom, sizeof(float));
|
|
|
|
pFile->Write_t( &ms_sstShadingType, sizeof(SurfaceShadingType));
|
|
pFile->Write_t( &ms_sttTranslucencyType, sizeof(SurfaceTranslucencyType));
|
|
(*pFile) << ms_ulRenderingFlags;
|
|
|
|
INDEX ctPolygons = ms_aiPolygons.Count();
|
|
(*pFile) << ctPolygons;
|
|
if( ctPolygons != 0)
|
|
{
|
|
pFile->Write_t( &ms_aiPolygons[0], sizeof( INDEX)*ctPolygons);
|
|
}
|
|
|
|
INDEX ctTextureVertices = ms_aiTextureVertices.Count();
|
|
(*pFile) << ctTextureVertices;
|
|
if( ctTextureVertices != 0)
|
|
{
|
|
pFile->Write_t( &ms_aiTextureVertices[0], sizeof( INDEX)*ctTextureVertices);
|
|
}
|
|
|
|
(*pFile) << ms_colColor;
|
|
|
|
(*pFile) << ms_colDiffuse;
|
|
(*pFile) << ms_colReflections;
|
|
(*pFile) << ms_colSpecular;
|
|
(*pFile) << ms_colBump;
|
|
|
|
(*pFile) << ms_ulOnColor;
|
|
(*pFile) << ms_ulOffColor;
|
|
}
|
|
|
|
void MappingSurface::WriteSettings_t( CTStream *pFile) // throw char *
|
|
{
|
|
(*pFile) << ms_Name;
|
|
(*pFile) << (INDEX &)ms_sstShadingType;
|
|
(*pFile) << (INDEX &)ms_sttTranslucencyType;
|
|
(*pFile) << ms_ulRenderingFlags;
|
|
(*pFile) << ms_colDiffuse;
|
|
(*pFile) << ms_colReflections;
|
|
(*pFile) << ms_colSpecular;
|
|
(*pFile) << ms_colBump;
|
|
(*pFile) << ms_ulOnColor;
|
|
(*pFile) << ms_ulOffColor;
|
|
}
|
|
|
|
void MappingSurface::ReadSettings_t( CTStream *pFile) // throw char *
|
|
{
|
|
(*pFile) >> ms_Name;
|
|
(*pFile) >> (INDEX&) ms_sstShadingType;
|
|
(*pFile) >> (INDEX&) ms_sttTranslucencyType;
|
|
(*pFile) >> ms_ulRenderingFlags;
|
|
(*pFile) >> ms_colDiffuse;
|
|
(*pFile) >> ms_colReflections;
|
|
(*pFile) >> ms_colSpecular;
|
|
(*pFile) >> ms_colBump;
|
|
(*pFile) >> ms_ulOnColor;
|
|
(*pFile) >> ms_ulOffColor;
|
|
}
|
|
|
|
//------------------------------------------ READ
|
|
void MappingSurface::Read_t( CTStream *pFile, BOOL bReadPolygonsPerSurface,
|
|
BOOL bReadSurfaceColors) // throw char *
|
|
{
|
|
(*pFile) >> ms_Name;
|
|
(*pFile) >> ms_vSurface2DOffset;
|
|
(*pFile) >> ms_HPB;
|
|
(*pFile) >> ms_Zoom;
|
|
|
|
if( bReadPolygonsPerSurface)
|
|
{
|
|
(*pFile) >> (INDEX &) ms_sstShadingType;
|
|
// WARNING !!! All shading types bigger than matte will be remaped into flat shading
|
|
// this was done when SHINY and METAL were removed
|
|
if( ms_sstShadingType > SST_MATTE)
|
|
{
|
|
ms_sstShadingType = SST_FLAT;
|
|
}
|
|
(*pFile) >> (INDEX &) ms_sttTranslucencyType;
|
|
(*pFile) >> ms_ulRenderingFlags;
|
|
if( (ms_ulRenderingFlags&SRF_NEW_TEXTURE_FORMAT) == 0)
|
|
ms_ulRenderingFlags |= SRF_DIFFUSE|SRF_NEW_TEXTURE_FORMAT;
|
|
if (ms_sttTranslucencyType==STT_TRANSLUCENT || ms_sttTranslucencyType==STT_ALPHAGOURAUD
|
|
||ms_sttTranslucencyType==STT_ADD||ms_sttTranslucencyType==STT_MULTIPLY) {
|
|
_bHasAlpha = TRUE;
|
|
}
|
|
|
|
ms_aiPolygons.Clear();
|
|
INDEX ctPolygons;
|
|
(*pFile) >> ctPolygons;
|
|
ms_aiPolygons.New( ctPolygons);
|
|
for (INDEX i = 0; i < ctPolygons; i++)
|
|
(*pFile) >> ms_aiPolygons[i];
|
|
|
|
ms_aiTextureVertices.Clear();
|
|
INDEX ctTextureVertices;
|
|
(*pFile) >> ctTextureVertices;
|
|
ms_aiTextureVertices.New( ctTextureVertices);
|
|
for (INDEX i = 0; i < ctTextureVertices; i++)
|
|
(*pFile) >> ms_aiTextureVertices[i];
|
|
|
|
(*pFile) >> ms_colColor;
|
|
}
|
|
if( bReadSurfaceColors)
|
|
{
|
|
(*pFile) >> ms_colDiffuse;
|
|
(*pFile) >> ms_colReflections;
|
|
(*pFile) >> ms_colSpecular;
|
|
(*pFile) >> ms_colBump;
|
|
(*pFile) >> ms_ulOnColor;
|
|
(*pFile) >> ms_ulOffColor;
|
|
}
|
|
}
|
|
|
|
|
|
void CModelData::LinkDataForSurfaces(BOOL bFirstMip)
|
|
{
|
|
INDEX iMipStart=1;
|
|
if( bFirstMip)
|
|
{
|
|
iMipStart=0;
|
|
}
|
|
// for each mip model
|
|
for( INDEX iMip=iMipStart; iMip<md_MipCt; iMip ++)
|
|
{
|
|
ModelMipInfo *pMMI = &md_MipInfos[ iMip];
|
|
|
|
// --------------------- Set index of transformed vertex to texture vertex
|
|
{for( INDEX iPolygon = 0; iPolygon<pMMI->mmpi_Polygons.Count(); iPolygon++)
|
|
{
|
|
for( INDEX iVertex = 0; iVertex<pMMI->mmpi_Polygons[iPolygon].mp_PolygonVertices.Count(); iVertex++)
|
|
{
|
|
ModelPolygonVertex *pmpvPolygonVertex = &pMMI->mmpi_Polygons[iPolygon].mp_PolygonVertices[iVertex];
|
|
INDEX iTransformed = md_TransformedVertices.Index( pmpvPolygonVertex->mpv_ptvTransformedVertex);
|
|
pmpvPolygonVertex->mpv_ptvTextureVertex->mtv_iTransformedVertex = iTransformed;
|
|
}
|
|
}}
|
|
|
|
// --------------------- Linking polygons for surface
|
|
// array telling how many polygons are in each surface
|
|
CStaticArray<INDEX> actPolygonsInSurface;
|
|
INDEX ctSurfaces = pMMI->mmpi_MappingSurfaces.Count();
|
|
actPolygonsInSurface.New( ctSurfaces);
|
|
// for each surface
|
|
{for( INDEX iSurface=0; iSurface<ctSurfaces; iSurface++)
|
|
{
|
|
// reset count of polygons
|
|
actPolygonsInSurface[iSurface] = 0;
|
|
}}
|
|
// for each polygon
|
|
{for( INDEX iPolygon=0; iPolygon<pMMI->mmpi_Polygons.Count(); iPolygon++)
|
|
{
|
|
// increment count of polygons in its surface
|
|
actPolygonsInSurface[ pMMI->mmpi_Polygons[iPolygon].mp_Surface]++;
|
|
}}
|
|
// for each surface
|
|
{for( INDEX iSurface=0; iSurface<ctSurfaces; iSurface++)
|
|
{
|
|
// allocate array for polygon indices
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons.Clear();
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons.New( actPolygonsInSurface[iSurface]);
|
|
if( actPolygonsInSurface[iSurface] != 0)
|
|
{
|
|
// last place in array will contain counter of added polygons
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons[actPolygonsInSurface[iSurface]-1] = 0;
|
|
}
|
|
}}
|
|
// for each polygon
|
|
for( INDEX iPolygon=0; iPolygon<pMMI->mmpi_Polygons.Count(); iPolygon++)
|
|
{
|
|
// get surface, polygons in surface and last remembered index of polygon in surface
|
|
INDEX iSurface = pMMI->mmpi_Polygons[iPolygon].mp_Surface;
|
|
INDEX ctPolygonsInSurface = actPolygonsInSurface[iSurface];
|
|
if( ctPolygonsInSurface != 0)
|
|
{
|
|
INDEX iLastSet = pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons[ ctPolygonsInSurface-1];
|
|
// remember last set polygon index
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons[ ctPolygonsInSurface-1] = iLastSet+1;
|
|
// remember index of polygon
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons[ iLastSet] = iPolygon;
|
|
}
|
|
}
|
|
|
|
// --------------------- Linking texture vertices for surface
|
|
// for each surface
|
|
{for( INDEX iSurface=0; iSurface<ctSurfaces; iSurface++)
|
|
{
|
|
CDynamicContainer<ModelTextureVertex> cmtvInSurface;
|
|
// for each polygon in surface
|
|
FOREACHINSTATICARRAY( pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons, INDEX, itimpo)
|
|
{
|
|
ModelPolygon &mpPolygon = pMMI->mmpi_Polygons[itimpo.Current()];
|
|
// for each vertex in polygon
|
|
for( INDEX iVertex=0; iVertex<mpPolygon.mp_PolygonVertices.Count(); iVertex++)
|
|
{
|
|
// get texture vertex
|
|
ModelTextureVertex *ptv =
|
|
mpPolygon.mp_PolygonVertices[ iVertex].mpv_ptvTextureVertex;
|
|
// if it is not added yet
|
|
if( !cmtvInSurface.IsMember( ptv))
|
|
{
|
|
// add it
|
|
cmtvInSurface.Add( ptv);
|
|
}
|
|
}
|
|
}
|
|
// add needed number of texture vertices
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiTextureVertices.Clear();
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiTextureVertices.New( cmtvInSurface.Count());
|
|
INDEX cttv = 0;
|
|
// for each texture vertex in container
|
|
FOREACHINDYNAMICCONTAINER(cmtvInSurface, ModelTextureVertex, itmtv)
|
|
{
|
|
INDEX idxtv = pMMI->mmpi_TextureVertices.Index( itmtv);
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_aiTextureVertices[cttv] = idxtv;
|
|
cttv++;
|
|
}
|
|
}}
|
|
}
|
|
}
|
|
|
|
// Default constructor
|
|
ModelMipInfo::ModelMipInfo(void)
|
|
{
|
|
mmpi_ulFlags = MM_PATCHES_VISIBLE | MM_ATTACHED_MODELS_VISIBLE;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
//------------------------------------------ WRITE
|
|
/*
|
|
* This is write function of one mip-model. It saves all mip's arrays eather by saving
|
|
* them really or calling their write functions.
|
|
*/
|
|
void ModelMipInfo::Write_t( CTStream *pFile) // throw char *
|
|
{
|
|
INDEX iMembersCt;
|
|
|
|
// Save count, call write for array of model polygons
|
|
pFile->WriteFullChunk_t( CChunkID("IPOL"), &mmpi_PolygonsCt, sizeof(INDEX));
|
|
{FOREACHINSTATICARRAY(mmpi_Polygons, ModelPolygon, it)
|
|
{ it.Current().Write_t( pFile);}}
|
|
|
|
// Save count, array of texture vertices
|
|
iMembersCt = mmpi_TextureVertices.Count();
|
|
(*pFile) << iMembersCt;
|
|
pFile->WriteFullChunk_t( CChunkID("TXV2"), &mmpi_TextureVertices[ 0], iMembersCt *
|
|
sizeof(struct ModelTextureVertex));
|
|
|
|
// Save count, call write for array of mapping surfaces
|
|
iMembersCt = mmpi_MappingSurfaces.Count();
|
|
(*pFile) << iMembersCt;
|
|
{FOREACHINSTATICARRAY(mmpi_MappingSurfaces, MappingSurface, it)
|
|
{ it.Current().Write_t( pFile);}}
|
|
|
|
// write mip model flags
|
|
(*pFile) << mmpi_ulFlags;
|
|
// write info of polygons occupied by patch
|
|
INDEX ctPatches = mmpi_aPolygonsPerPatch.Count();
|
|
(*pFile) << ctPatches;
|
|
// for each patch
|
|
for( INDEX iPatch=0; iPatch<ctPatches; iPatch++)
|
|
{
|
|
// write no of occupied polygons
|
|
INDEX ctOccupied = mmpi_aPolygonsPerPatch[iPatch].ppp_iPolygons.Count();
|
|
(*pFile) << ctOccupied;
|
|
if( ctOccupied != 0)
|
|
{
|
|
pFile->WriteFullChunk_t( CChunkID("OCPL"),
|
|
&mmpi_aPolygonsPerPatch[iPatch].ppp_iPolygons[ 0], ctOccupied * sizeof(INDEX));
|
|
}
|
|
}
|
|
}
|
|
//------------------------------------------ READ
|
|
/*
|
|
* This is read function of one mip-model
|
|
*/
|
|
void ModelMipInfo::Read_t(CTStream *pFile,
|
|
BOOL bReadPolygonalPatches,
|
|
BOOL bReadPolygonsPerSurface,
|
|
BOOL bReadSurfaceColors)
|
|
{
|
|
INDEX iMembersCt;
|
|
|
|
// Load count, allocate array and call Read for array of model polygons
|
|
pFile->ReadFullChunk_t( CChunkID("IPOL"), &mmpi_PolygonsCt, sizeof(INDEX));
|
|
BYTESWAP(mmpi_PolygonsCt);
|
|
mmpi_Polygons.New( mmpi_PolygonsCt);
|
|
{FOREACHINSTATICARRAY(mmpi_Polygons, ModelPolygon, it)
|
|
{
|
|
it.Current().Read_t( pFile);
|
|
}}
|
|
|
|
// Load count, allocate and load array of texture vertices
|
|
(*pFile) >> iMembersCt;
|
|
mmpi_TextureVertices.New( iMembersCt);
|
|
if( bReadPolygonsPerSurface)
|
|
{
|
|
// chunk ID will tell us if we should read new format that contains bump normals
|
|
CChunkID idChunk = pFile->GetID_t();
|
|
// jump over chunk size
|
|
ULONG ulDummySize;
|
|
(*pFile) >> ulDummySize;
|
|
// if bump normals are saved (new format)
|
|
if( idChunk == CChunkID("TXV2"))
|
|
{
|
|
for (SLONG i = 0; i < iMembersCt; i++)
|
|
(*pFile)>>mmpi_TextureVertices[i];
|
|
} else {
|
|
// bump normals are not saved
|
|
for( INDEX iVertex = 0; iVertex<iMembersCt; iVertex++)
|
|
{
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_UVW;
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_UV;
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_Done;
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_iTransformedVertex;
|
|
mmpi_TextureVertices[ iVertex].mtv_vU = FLOAT3D(0,0,0);
|
|
mmpi_TextureVertices[ iVertex].mtv_vV = FLOAT3D(0,0,0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pFile->ExpectID_t( CChunkID("TXVT"));
|
|
// jump over chunk size
|
|
ULONG ulDummySize;
|
|
(*pFile) >> ulDummySize;
|
|
// read models in old format
|
|
for( INDEX iVertex = 0; iVertex<iMembersCt; iVertex++)
|
|
{
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_UVW;
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_UV;
|
|
(*pFile)>>mmpi_TextureVertices[ iVertex].mtv_Done;
|
|
mmpi_TextureVertices[ iVertex].mtv_iTransformedVertex = 0;
|
|
mmpi_TextureVertices[ iVertex].mtv_vU = FLOAT3D(0,0,0);
|
|
mmpi_TextureVertices[ iVertex].mtv_vV = FLOAT3D(0,0,0);
|
|
}
|
|
}
|
|
|
|
// Load count, allcate array and call Read for array of mapping surfaces
|
|
(*pFile) >> iMembersCt;
|
|
mmpi_MappingSurfaces.New( iMembersCt);
|
|
INDEX iIndexOfSurface = 0;
|
|
{FOREACHINSTATICARRAY(mmpi_MappingSurfaces, MappingSurface, it)
|
|
{
|
|
it.Current().Read_t( pFile, bReadPolygonsPerSurface, bReadSurfaceColors);
|
|
// obtain color per surface from polygons (old model format)
|
|
if( !bReadPolygonsPerSurface)
|
|
{
|
|
// we will copy color from first polygon found with this surface into color of surface
|
|
it->ms_colColor = C_WHITE;
|
|
// for all polygons in this mip level
|
|
for( INDEX iPolygon=0;iPolygon<mmpi_PolygonsCt;iPolygon++)
|
|
{
|
|
if( mmpi_Polygons[ iPolygon].mp_Surface == iIndexOfSurface)
|
|
{
|
|
it->ms_colColor = mmpi_Polygons[ iPolygon].mp_ColorAndAlpha;
|
|
break;
|
|
}
|
|
}
|
|
iIndexOfSurface++;
|
|
}
|
|
}}
|
|
|
|
if( bReadPolygonalPatches)
|
|
{
|
|
// read mip model flags
|
|
(*pFile) >> mmpi_ulFlags;
|
|
// read no of patches
|
|
INDEX ctPatches;
|
|
(*pFile) >> ctPatches;
|
|
if( ctPatches != 0)
|
|
{
|
|
mmpi_aPolygonsPerPatch.New( ctPatches);
|
|
// read info for polygonal patches
|
|
for( INDEX iPatch=0; iPatch<ctPatches; iPatch++)
|
|
{
|
|
// read no of occupied polygons
|
|
INDEX ctOccupied;
|
|
(*pFile) >> ctOccupied;
|
|
if( ctOccupied != 0)
|
|
{
|
|
mmpi_aPolygonsPerPatch[iPatch].ppp_iPolygons.New( ctOccupied);
|
|
pFile->ReadFullChunk_t( CChunkID("OCPL"),
|
|
&mmpi_aPolygonsPerPatch[iPatch].ppp_iPolygons[ 0], ctOccupied * sizeof(INDEX));
|
|
|
|
#if PLATFORM_BIGENDIAN
|
|
for (INDEX i = 0; i < ctOccupied; i++)
|
|
{
|
|
BYTESWAP(mmpi_aPolygonsPerPatch[iPatch].ppp_iPolygons[i]);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine converts mpv_ptvTransformedVertex and mpv_ptvTextureVertex from ptrs to Indices
|
|
*/
|
|
void CModelData::PtrsToIndices()
|
|
{
|
|
INDEX i, j;
|
|
|
|
for( i=0; i<md_MipCt; i++)
|
|
{
|
|
FOREACHINSTATICARRAY(md_MipInfos[ i].mmpi_Polygons, ModelPolygon, it1)
|
|
{
|
|
FOREACHINSTATICARRAY(it1.Current().mp_PolygonVertices, ModelPolygonVertex, it2)
|
|
{
|
|
for( j=0; j<md_TransformedVertices.Count(); j++)
|
|
{
|
|
if( it2.Current().mpv_ptvTransformedVertex == &md_TransformedVertices[ j])
|
|
break;
|
|
}
|
|
it2.Current().mpv_ptvTransformedVertex = (struct TransformedVertexData *)(size_t)j;
|
|
|
|
for( j=0; j<md_MipInfos[ i].mmpi_TextureVertices.Count(); j++)
|
|
{
|
|
if( it2.Current().mpv_ptvTextureVertex == &md_MipInfos[ i].mmpi_TextureVertices[ j])
|
|
break;
|
|
}
|
|
it2.Current().mpv_ptvTextureVertex = (ModelTextureVertex *)(size_t)j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine converts mpv_ptvTransformedVertex and mpv_ptvTextureVertex from Indices to ptrs
|
|
*/
|
|
void CModelData::IndicesToPtrs()
|
|
{
|
|
INDEX i, j;
|
|
|
|
for( i=0; i<md_MipCt; i++)
|
|
{
|
|
FOREACHINSTATICARRAY(md_MipInfos[ i].mmpi_Polygons, ModelPolygon, it1)
|
|
{
|
|
FOREACHINSTATICARRAY(it1.Current().mp_PolygonVertices, ModelPolygonVertex, it2)
|
|
{
|
|
struct ModelPolygonVertex * pMPV = &it2.Current();
|
|
// DG: this looks like a 64-bit issue but is most probably ok, as the pointers
|
|
// should contain indices from PtrToIndices()
|
|
j = (INDEX) (size_t) it2.Current().mpv_ptvTransformedVertex;
|
|
it2.Current().mpv_ptvTransformedVertex = &md_TransformedVertices[ j];
|
|
// DG: same here
|
|
j = (INDEX) (size_t) it2.Current().mpv_ptvTextureVertex;
|
|
it2.Current().mpv_ptvTextureVertex = &md_MipInfos[ i].mmpi_TextureVertices[ j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CModelCollisionBox::CModelCollisionBox(void)
|
|
{
|
|
mcb_vCollisionBoxMin = FLOAT3D( -0.5f, 0.0f,-0.5f);
|
|
mcb_vCollisionBoxMax = FLOAT3D( 0.5f, 2.0f, 0.5f);
|
|
mcb_iCollisionBoxDimensionEquality = LENGTH_EQ_WIDTH;
|
|
mcb_strName = "PART_NAME";
|
|
}
|
|
|
|
void CModelCollisionBox::Read_t(CTStream *istrFile)
|
|
{
|
|
// Read collision box min
|
|
(*istrFile) >> mcb_vCollisionBoxMin;
|
|
// Read collision box size
|
|
(*istrFile) >> mcb_vCollisionBoxMax;
|
|
// Get "colision box dimensions equality" value
|
|
if( (mcb_vCollisionBoxMax(2)-mcb_vCollisionBoxMin(2)) ==
|
|
(mcb_vCollisionBoxMax(1)-mcb_vCollisionBoxMin(1)) )
|
|
{
|
|
mcb_iCollisionBoxDimensionEquality = HEIGHT_EQ_WIDTH;
|
|
}
|
|
else if( (mcb_vCollisionBoxMax(3)-mcb_vCollisionBoxMin(3)) ==
|
|
(mcb_vCollisionBoxMax(1)-mcb_vCollisionBoxMin(1)) )
|
|
{
|
|
mcb_iCollisionBoxDimensionEquality = LENGTH_EQ_WIDTH;
|
|
}
|
|
else if( (mcb_vCollisionBoxMax(3)-mcb_vCollisionBoxMin(3)) ==
|
|
(mcb_vCollisionBoxMax(2)-mcb_vCollisionBoxMin(2)) )
|
|
{
|
|
mcb_iCollisionBoxDimensionEquality = LENGTH_EQ_HEIGHT;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
// Force them to be legal (Length = Width)
|
|
mcb_vCollisionBoxMax(3) = mcb_vCollisionBoxMin(3) +
|
|
(mcb_vCollisionBoxMax(1)-mcb_vCollisionBoxMin(1));
|
|
*/
|
|
mcb_iCollisionBoxDimensionEquality = LENGTH_EQ_WIDTH;
|
|
}
|
|
}
|
|
|
|
void CModelCollisionBox::ReadName_t(CTStream *istrFile)
|
|
{
|
|
// read collision box name
|
|
(*istrFile)>>mcb_strName;
|
|
}
|
|
|
|
void CModelCollisionBox::Write_t(CTStream *ostrFile)
|
|
{
|
|
// Write collision box min
|
|
(*ostrFile) << mcb_vCollisionBoxMin;
|
|
// Write collision box size
|
|
(*ostrFile) << mcb_vCollisionBoxMax;
|
|
// write collision box name
|
|
(*ostrFile) << mcb_strName;
|
|
}
|
|
|
|
CAttachedModelPosition::CAttachedModelPosition( void)
|
|
{
|
|
amp_iCenterVertex = 0;
|
|
amp_iFrontVertex = 1;
|
|
amp_iUpVertex = 2;
|
|
amp_plRelativePlacement = CPlacement3D( FLOAT3D(0,0,0), ANGLE3D(0,0,0));
|
|
}
|
|
|
|
void CAttachedModelPosition::Read_t( CTStream *strFile)
|
|
{
|
|
*strFile >> amp_iCenterVertex;
|
|
*strFile >> amp_iFrontVertex;
|
|
*strFile >> amp_iUpVertex;
|
|
*strFile >> amp_plRelativePlacement;
|
|
}
|
|
|
|
void CAttachedModelPosition::Write_t( CTStream *strFile)
|
|
{
|
|
*strFile << amp_iCenterVertex;
|
|
*strFile << amp_iFrontVertex;
|
|
*strFile << amp_iUpVertex;
|
|
*strFile << amp_plRelativePlacement;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
//------------------------------------------ WRITE
|
|
void CModelData::Write_t( CTStream *pFile) // throw char *
|
|
{
|
|
INDEX i;
|
|
|
|
PtrsToIndices();
|
|
// Save main ID
|
|
pFile->WriteID_t( CChunkID("MDAT"));
|
|
|
|
// Save version number
|
|
pFile->WriteID_t( CChunkID( MODEL_VERSION));
|
|
|
|
// Save flags
|
|
(*pFile) << md_Flags;
|
|
|
|
#if PLATFORM_BIGENDIAN
|
|
STUBBED("byte order");
|
|
#endif
|
|
|
|
// Save vertices and frames ct
|
|
pFile->WriteFullChunk_t( CChunkID("IVTX"), &md_VerticesCt, sizeof(INDEX));
|
|
pFile->WriteFullChunk_t( CChunkID("IFRM"), &md_FramesCt, sizeof(INDEX));
|
|
|
|
// write array of 8-bit or 16-bit compressed vertices
|
|
if( md_Flags & MF_COMPRESSED_16BIT)
|
|
{
|
|
pFile->WriteFullChunk_t( CChunkID("AV17"), &md_FrameVertices16[ 0], md_VerticesCt * md_FramesCt *
|
|
sizeof(struct ModelFrameVertex16));
|
|
}
|
|
else
|
|
{
|
|
pFile->WriteFullChunk_t( CChunkID("AFVX"), &md_FrameVertices8[ 0], md_VerticesCt * md_FramesCt *
|
|
sizeof(struct ModelFrameVertex8));
|
|
}
|
|
// Save frame info array
|
|
pFile->WriteFullChunk_t( CChunkID("AFIN"), &md_FrameInfos[ 0], md_FramesCt *
|
|
sizeof(struct ModelFrameInfo));
|
|
// Save frame main mip vertices array
|
|
pFile->WriteFullChunk_t( CChunkID("AMMV"), &md_MainMipVertices[ 0], md_VerticesCt *
|
|
sizeof(FLOAT3D));
|
|
// Save vertex mip-mask array
|
|
pFile->WriteFullChunk_t( CChunkID("AVMK"), &md_VertexMipMask[ 0], md_VerticesCt *
|
|
sizeof(ULONG));
|
|
// Save mip levels counter
|
|
pFile->WriteFullChunk_t( CChunkID("IMIP"), &md_MipCt, sizeof(INDEX));
|
|
|
|
// Save mip factors array
|
|
pFile->WriteFullChunk_t( CChunkID("FMIP"), &md_MipSwitchFactors[ 0], MAX_MODELMIPS * sizeof(float));
|
|
|
|
// Save all model mip infos
|
|
for( i=0; i<md_MipCt; i++) md_MipInfos[ i].Write_t( pFile);
|
|
|
|
// Save patches
|
|
pFile->WriteID_t( CChunkID("PTC2"));
|
|
for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++) md_mpPatches[ iPatch].Write_t(pFile);
|
|
|
|
// Save texture width and height in MEX-es
|
|
pFile->WriteFullChunk_t( CChunkID("STXW"), &md_Width, sizeof(MEX));
|
|
pFile->WriteFullChunk_t( CChunkID("STXH"), &md_Height, sizeof(MEX));
|
|
|
|
// Save value for shading type
|
|
pFile->Write_t( &md_ShadowQuality, sizeof(SLONG));
|
|
|
|
// Save static stretch value
|
|
pFile->Write_t( &md_Stretch, sizeof(FLOAT3D));
|
|
|
|
// Save model offset
|
|
pFile->Write_t( &md_vCenter, sizeof(FLOAT3D));
|
|
|
|
// Save count of collision boxes
|
|
INDEX ctCollisionBoxes = md_acbCollisionBox.Count();
|
|
pFile->Write_t( &ctCollisionBoxes, sizeof(INDEX));
|
|
md_acbCollisionBox.Lock();
|
|
// save all collision boxes
|
|
for( INDEX iCollisionBox=0; iCollisionBox<ctCollisionBoxes; iCollisionBox++)
|
|
{
|
|
// save current collision box
|
|
md_acbCollisionBox[ iCollisionBox].Write_t( pFile);
|
|
}
|
|
md_acbCollisionBox.Unlock();
|
|
|
|
// save boolean defining collision type for this model
|
|
pFile->WriteID_t( CChunkID( "COLI"));
|
|
*pFile << md_bCollideAsCube;
|
|
|
|
// Save count of attached positions
|
|
INDEX ctAttachedPositions = md_aampAttachedPosition.Count();
|
|
*pFile << ctAttachedPositions;
|
|
FOREACHINDYNAMICARRAY(md_aampAttachedPosition, CAttachedModelPosition, itamp)
|
|
{
|
|
itamp->Write_t(pFile);
|
|
}
|
|
|
|
// Save color names (get count of valid names, write count and then write existing names)
|
|
INDEX iValidColorsCt = 0;
|
|
for( i=0; i<MAX_COLOR_NAMES; i++)
|
|
if( md_ColorNames[ i] != "")
|
|
iValidColorsCt++;
|
|
pFile->WriteFullChunk_t( CChunkID("ICLN"), &iValidColorsCt, sizeof(INDEX));
|
|
for( i=0; i<MAX_COLOR_NAMES; i++)
|
|
{
|
|
if( md_ColorNames[ i] != "")
|
|
{
|
|
*pFile << i;
|
|
*pFile << md_ColorNames[ i];
|
|
}
|
|
}
|
|
|
|
// Save AnimData
|
|
CAnimData::Write_t( pFile);
|
|
IndicesToPtrs();
|
|
|
|
*pFile << md_colDiffuse;
|
|
*pFile << md_colReflections;
|
|
*pFile << md_colSpecular;
|
|
*pFile << md_colBump;
|
|
}
|
|
|
|
|
|
//------------------------------------------ READ
|
|
void CModelData::Read_t( CTStream *pFile) // throw char *
|
|
{
|
|
INDEX i;
|
|
|
|
_bHasAlpha = FALSE;
|
|
// Read main ID
|
|
pFile->ExpectID_t( CChunkID("MDAT"));
|
|
|
|
// Check version number
|
|
BOOL bHasSavedCenter = FALSE;
|
|
BOOL bHasMultipleCollisionBoxes = FALSE;
|
|
BOOL bHasAttachedPositions = FALSE;
|
|
BOOL bHasPolygonalPatches = FALSE;
|
|
BOOL bHasPolygonsPerSurface = FALSE;
|
|
BOOL bHasSavedFlagsOnStart = FALSE;
|
|
BOOL bHasColorForReflectionAndSpecularity = FALSE;
|
|
BOOL bHasDiffuseColor = FALSE;
|
|
// get version ID
|
|
CChunkID idVersion = pFile->GetID_t();
|
|
// if this is version without stretch center then it doesn't contain multiple
|
|
// collision boxes also
|
|
if( CChunkID( MODEL_VERSION_WITHOUT_STRETCH_CENTER) == idVersion)
|
|
{
|
|
}
|
|
// if model has stretch center but does not have multiple collision boxes
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_MULTIPLE_COLLISION_BOXES) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
}
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_ATTACHED_POSITIONS) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
}
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_POLYGONAL_PATCHES) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
bHasAttachedPositions = TRUE;
|
|
}
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_POLYGONS_PER_SURFACE) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
bHasAttachedPositions = TRUE;
|
|
bHasPolygonalPatches = TRUE;
|
|
}
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_16_BIT_COMPRESSION) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
bHasAttachedPositions = TRUE;
|
|
bHasPolygonalPatches = TRUE;
|
|
bHasPolygonsPerSurface = TRUE;
|
|
}
|
|
// if has saved flags on start - because 16-bit compression
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_REFLECTION_AND_SPECULARITY) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
bHasAttachedPositions = TRUE;
|
|
bHasPolygonalPatches = TRUE;
|
|
bHasPolygonsPerSurface = TRUE;
|
|
bHasSavedFlagsOnStart = TRUE;
|
|
}
|
|
// has saved color for reflection and specularity
|
|
else if( CChunkID( MODEL_VERSION_WITHOUT_DIFFUSE_COLOR) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
bHasAttachedPositions = TRUE;
|
|
bHasPolygonalPatches = TRUE;
|
|
bHasPolygonsPerSurface = TRUE;
|
|
bHasSavedFlagsOnStart = TRUE;
|
|
bHasColorForReflectionAndSpecularity = TRUE;
|
|
}
|
|
// has saved diffuse color
|
|
else if( CChunkID( MODEL_VERSION) == idVersion)
|
|
{
|
|
bHasSavedCenter = TRUE;
|
|
bHasMultipleCollisionBoxes = TRUE;
|
|
bHasAttachedPositions = TRUE;
|
|
bHasPolygonalPatches = TRUE;
|
|
bHasPolygonsPerSurface = TRUE;
|
|
bHasSavedFlagsOnStart = TRUE;
|
|
bHasColorForReflectionAndSpecularity = TRUE;
|
|
bHasDiffuseColor = TRUE;
|
|
}
|
|
else
|
|
{
|
|
throw(TRANS("Invalid model version."));
|
|
}
|
|
|
|
if( bHasSavedFlagsOnStart)
|
|
{
|
|
(*pFile)>>md_Flags;
|
|
}
|
|
|
|
// Read vertices and frames ct
|
|
pFile->ReadFullChunk_t( CChunkID("IVTX"), &md_VerticesCt, sizeof(INDEX));
|
|
BYTESWAP(md_VerticesCt);
|
|
md_TransformedVertices.New( md_VerticesCt);
|
|
pFile->ReadFullChunk_t( CChunkID("IFRM"), &md_FramesCt, sizeof(INDEX));
|
|
BYTESWAP(md_FramesCt);
|
|
|
|
// read array of 8-bit or 16-bit compressed vertices
|
|
if( md_Flags & MF_COMPRESSED_16BIT)
|
|
{
|
|
md_FrameVertices16.New( md_VerticesCt * md_FramesCt);
|
|
CChunkID cidVerticesChunk = pFile->PeekID_t();
|
|
// if we are loading model in old 16-bit compressed format (normals use 1 byte)
|
|
if( cidVerticesChunk == CChunkID("AV16"))
|
|
{
|
|
CChunkID cidDummy = pFile->GetID_t();
|
|
ULONG ulDummy;
|
|
// skip chunk size
|
|
*pFile >> ulDummy;
|
|
for( INDEX iVtx=0; iVtx<md_VerticesCt * md_FramesCt; iVtx++)
|
|
{
|
|
(*pFile)>>md_FrameVertices16[iVtx];
|
|
// convert 8-bit normal from index into normal defined using heading and pitch
|
|
INDEX i8BitNormalIndex = md_FrameVertices16[iVtx].mfv_ubNormH;
|
|
const FLOAT3D &vNormal = avGouraudNormals[i8BitNormalIndex];
|
|
CompressNormal_HQ( vNormal, md_FrameVertices16[iVtx].mfv_ubNormH, md_FrameVertices16[iVtx].mfv_ubNormP);
|
|
}
|
|
}
|
|
// load new 16-bit compressed format (normals use 2 byte) model
|
|
else if( cidVerticesChunk == CChunkID("AV17"))
|
|
{
|
|
pFile->ReadFullChunk_t( CChunkID("AV17"), &md_FrameVertices16[ 0], md_VerticesCt * md_FramesCt *
|
|
sizeof(struct ModelFrameVertex16));
|
|
|
|
#if PLATFORM_BIGENDIAN
|
|
for (ULONG i = 0; i < md_VerticesCt * md_FramesCt; i++)
|
|
{
|
|
BYTESWAP(md_FrameVertices16[i].mfv_SWPoint.vector[0]);
|
|
BYTESWAP(md_FrameVertices16[i].mfv_SWPoint.vector[1]);
|
|
BYTESWAP(md_FrameVertices16[i].mfv_SWPoint.vector[2]);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ThrowF_t( TRANS("Expecting chunk ID for model frame vertices but found %s"), (const char *) cidVerticesChunk);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
md_FrameVertices8.New( md_VerticesCt * md_FramesCt);
|
|
pFile->ReadFullChunk_t( CChunkID("AFVX"), &md_FrameVertices8[ 0], md_VerticesCt * md_FramesCt *
|
|
sizeof(struct ModelFrameVertex8));
|
|
}
|
|
|
|
// Allocate and Read frame info array
|
|
md_FrameInfos.New( md_FramesCt);
|
|
pFile->ReadFullChunk_t( CChunkID("AFIN"), &md_FrameInfos[0], md_FramesCt * sizeof(struct ModelFrameInfo));
|
|
#if PLATFORM_BIGENDIAN
|
|
for (ULONG i = 0; i < md_FramesCt; i++)
|
|
{
|
|
BYTESWAP(md_FrameInfos[i].mfi_Box.minvect.vector[0]);
|
|
BYTESWAP(md_FrameInfos[i].mfi_Box.minvect.vector[1]);
|
|
BYTESWAP(md_FrameInfos[i].mfi_Box.minvect.vector[2]);
|
|
BYTESWAP(md_FrameInfos[i].mfi_Box.maxvect.vector[0]);
|
|
BYTESWAP(md_FrameInfos[i].mfi_Box.maxvect.vector[1]);
|
|
BYTESWAP(md_FrameInfos[i].mfi_Box.maxvect.vector[2]);
|
|
}
|
|
#endif
|
|
|
|
// Allocate Read frame main mip vertices array
|
|
md_MainMipVertices.New( md_VerticesCt);
|
|
pFile->ReadFullChunk_t( CChunkID("AMMV"), &md_MainMipVertices[0], md_VerticesCt * sizeof(FLOAT3D));
|
|
#if PLATFORM_BIGENDIAN
|
|
for (ULONG i = 0; i < md_VerticesCt; i++)
|
|
{
|
|
BYTESWAP(md_MainMipVertices[i].vector[0]);
|
|
BYTESWAP(md_MainMipVertices[i].vector[1]);
|
|
BYTESWAP(md_MainMipVertices[i].vector[2]);
|
|
}
|
|
#endif
|
|
|
|
// Allocate and Read vertex mip-mask array
|
|
md_VertexMipMask.New( md_VerticesCt);
|
|
pFile->ReadFullChunk_t( CChunkID("AVMK"), &md_VertexMipMask[0], md_VerticesCt * sizeof(ULONG));
|
|
#if PLATFORM_BIGENDIAN
|
|
for (ULONG i = 0; i < md_VerticesCt; i++)
|
|
{
|
|
BYTESWAP(md_VertexMipMask[i]);
|
|
}
|
|
#endif
|
|
|
|
// Read mip levels counter
|
|
pFile->ReadFullChunk_t( CChunkID("IMIP"), &md_MipCt, sizeof(INDEX));
|
|
BYTESWAP(md_MipCt);
|
|
|
|
// Read mip factors array
|
|
pFile->ReadFullChunk_t( CChunkID("FMIP"), &md_MipSwitchFactors[0], MAX_MODELMIPS * sizeof(float));
|
|
#if PLATFORM_BIGENDIAN
|
|
for (ULONG i = 0; i < MAX_MODELMIPS; i++)
|
|
{
|
|
BYTESWAP(md_MipSwitchFactors[i]);
|
|
}
|
|
#endif
|
|
|
|
// Read all model mip infos
|
|
INDEX ctMipsRejected=0;
|
|
for( i=0; i<md_MipCt; i++) {
|
|
ModelMipInfo mmiDummy; // need one dummy mipmodel info in case of mip level rejection
|
|
// reject mip model in case its even, and not last
|
|
if( !mdl_bFineQuality && (i%2)==1 && i!=(md_MipCt-1)) {
|
|
mmiDummy.Read_t( pFile, bHasPolygonalPatches, bHasPolygonsPerSurface, bHasDiffuseColor);
|
|
mmiDummy.Clear();
|
|
ctMipsRejected++;
|
|
} else {
|
|
// Notice that model's difuse color has been saved in same model format change when surface color has been added
|
|
md_MipInfos[i-ctMipsRejected].Read_t( pFile, bHasPolygonalPatches, bHasPolygonsPerSurface, bHasDiffuseColor);
|
|
// readjust mip scaling factors (if)
|
|
if( i>0) md_MipSwitchFactors[i-ctMipsRejected-1] = md_MipSwitchFactors[i-1];
|
|
}
|
|
}
|
|
// readjust last mip scaling factor
|
|
md_MipSwitchFactors[i-ctMipsRejected-1] = md_MipSwitchFactors[i-1];
|
|
// reduce mip level count
|
|
md_MipCt -= ctMipsRejected;
|
|
|
|
// if patches are saved in old format
|
|
CChunkID cidPatchChunkID = pFile->PeekID_t();
|
|
if( cidPatchChunkID == CChunkID("STMK"))
|
|
{
|
|
ULONG ulOldExistingPatches;
|
|
pFile->ReadFullChunk_t( CChunkID("STMK"), &ulOldExistingPatches, sizeof(ULONG));
|
|
BYTESWAP(ulOldExistingPatches);
|
|
|
|
for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
|
|
{
|
|
if( ((1UL << iPatch) & ulOldExistingPatches) != 0)
|
|
{
|
|
CTFileName fnPatchName;
|
|
*pFile >> fnPatchName;
|
|
try
|
|
{
|
|
md_mpPatches[ iPatch].mp_toTexture.SetData_t( fnPatchName);
|
|
}
|
|
catch(char *strError)
|
|
{
|
|
(void) strError;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if patches are saved in new format
|
|
else if( cidPatchChunkID == CChunkID("PTC2"))
|
|
{
|
|
pFile->ExpectID_t( CChunkID("PTC2"));
|
|
for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
|
|
{
|
|
try
|
|
{
|
|
md_mpPatches[ iPatch].Read_t(pFile);
|
|
}
|
|
catch(char *strError)
|
|
{
|
|
(void) strError;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ThrowF_t(TRANS("Expecting chunk containing patch data but found unrecognisable chunk ID."));
|
|
}
|
|
|
|
// Read texture width and height in MEX-es
|
|
pFile->ReadFullChunk_t( CChunkID("STXW"), &md_Width, sizeof(MEX));
|
|
pFile->ReadFullChunk_t( CChunkID("STXH"), &md_Height, sizeof(MEX));
|
|
BYTESWAP(md_Width);
|
|
BYTESWAP(md_Height);
|
|
|
|
// in old patch format, now patch postiions are loaded
|
|
if( cidPatchChunkID == CChunkID("STMK"))
|
|
{
|
|
pFile->ExpectID_t( CChunkID("POSS"));
|
|
ULONG ulChunkSize;
|
|
*pFile >> ulChunkSize;
|
|
for( INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
|
|
{
|
|
// Read patch position
|
|
*pFile >> md_mpPatches[ iPatch].mp_mexPosition;
|
|
}
|
|
}
|
|
|
|
|
|
if( !bHasSavedFlagsOnStart)
|
|
{
|
|
// Read flags
|
|
(*pFile)>>md_Flags;
|
|
}
|
|
|
|
// Read value for shading type
|
|
(*pFile)>>md_ShadowQuality;
|
|
|
|
// Read static stretch value
|
|
(*pFile)>>md_Stretch;
|
|
|
|
// if this is model with saved center
|
|
if( bHasSavedCenter) { // read it
|
|
(*pFile)>>md_vCenter;
|
|
}
|
|
// this model has been saved without center point
|
|
else { // so just reset it
|
|
md_vCenter = FLOAT3D(0,0,0);
|
|
}
|
|
|
|
// convert model to 8-bit if requested and needed
|
|
if( !mdl_bFineQuality && (md_Flags&MF_COMPRESSED_16BIT))
|
|
{
|
|
// prepare 8-bit frame vertices array
|
|
const INDEX ctVtx = md_VerticesCt * md_FramesCt;
|
|
md_FrameVertices8.New(ctVtx);
|
|
|
|
// loop thru vertices
|
|
for( INDEX iVtx=0; iVtx<ctVtx; iVtx++) {
|
|
ModelFrameVertex16 &mfv16 = md_FrameVertices16[iVtx];
|
|
ModelFrameVertex8 &mfv8 = md_FrameVertices8[iVtx];
|
|
// convert vertex coordinate
|
|
mfv8.mfv_SBPoint(1) = mfv16.mfv_SWPoint(1) >>8;
|
|
mfv8.mfv_SBPoint(2) = mfv16.mfv_SWPoint(2) >>8;
|
|
mfv8.mfv_SBPoint(3) = mfv16.mfv_SWPoint(3) >>8;
|
|
// convert normal
|
|
const INDEX iHofs = mfv16.mfv_ubNormH>>1;
|
|
const INDEX iPofs = mfv16.mfv_ubNormP>>1;
|
|
mfv8.mfv_NormIndex = aubGouraudConv[iHofs*128+iPofs];
|
|
}
|
|
|
|
// done with conversion
|
|
md_Stretch *= 256.0f;
|
|
md_FrameVertices16.Clear();
|
|
md_Flags &= ~MF_COMPRESSED_16BIT;
|
|
}
|
|
|
|
// create compressed vector center that will be used for setting object handle
|
|
md_vCompressedCenter(1) = -md_vCenter(1)/md_Stretch(1);
|
|
md_vCompressedCenter(2) = -md_vCenter(2)/md_Stretch(2);
|
|
md_vCompressedCenter(3) = -md_vCenter(3)/md_Stretch(3);
|
|
|
|
// if model has been saved with multiple collision boxes
|
|
if( bHasMultipleCollisionBoxes)
|
|
{
|
|
INDEX ctCollisionBoxes;
|
|
// get count of collision boxes
|
|
(*pFile)>>ctCollisionBoxes;
|
|
// add needed ammount of members
|
|
md_acbCollisionBox.New( ctCollisionBoxes);
|
|
md_acbCollisionBox.Lock();
|
|
// for all saved collision boxes
|
|
for( INDEX iCollisionBox=0; iCollisionBox<ctCollisionBoxes; iCollisionBox++)
|
|
{
|
|
// load current collision box from stream (without name)
|
|
md_acbCollisionBox[iCollisionBox].Read_t( pFile);
|
|
// load name manualy
|
|
md_acbCollisionBox[iCollisionBox].ReadName_t( pFile);
|
|
}
|
|
md_acbCollisionBox.Unlock();
|
|
}
|
|
// else add one collision box and load it manually
|
|
else
|
|
{
|
|
// add one collision box
|
|
md_acbCollisionBox.New();
|
|
md_acbCollisionBox.Lock();
|
|
// read one collision box manualy (without name)
|
|
md_acbCollisionBox[ 0].Read_t( pFile);
|
|
md_acbCollisionBox.Unlock();
|
|
}
|
|
|
|
// peek chunk ID and see if we should read boolean defining collision type (speheres or cube)
|
|
if ( pFile->PeekID_t()==CChunkID("COLI"))
|
|
{
|
|
pFile->ExpectID_t("COLI");
|
|
*pFile >> md_bCollideAsCube;
|
|
}
|
|
else
|
|
{
|
|
md_bCollideAsCube = FALSE;
|
|
}
|
|
|
|
// if we should read attached positions
|
|
if( bHasAttachedPositions)
|
|
{
|
|
// read count of attached positions
|
|
INDEX ctAttachedPositions;
|
|
*pFile >> ctAttachedPositions;
|
|
md_aampAttachedPosition.New(ctAttachedPositions);
|
|
FOREACHINDYNAMICARRAY(md_aampAttachedPosition, CAttachedModelPosition, itamp)
|
|
{
|
|
itamp->Read_t(pFile);
|
|
// clamp vertices to no of model data vertices
|
|
itamp->amp_iCenterVertex = Clamp( itamp->amp_iCenterVertex, (INDEX) 0, md_MainMipVertices.Count());
|
|
itamp->amp_iFrontVertex = Clamp( itamp->amp_iFrontVertex, (INDEX) 0, md_MainMipVertices.Count());
|
|
itamp->amp_iUpVertex = Clamp( itamp->amp_iUpVertex, (INDEX) 0, md_MainMipVertices.Count());
|
|
}
|
|
}
|
|
|
|
// Read color names (Read count, read existing names)
|
|
INDEX iValidColorsCt;
|
|
pFile->ReadFullChunk_t( CChunkID("ICLN"), &iValidColorsCt, sizeof(INDEX));
|
|
for( i=0; i<iValidColorsCt; i++)
|
|
{
|
|
INDEX iExistingColorName;
|
|
*pFile >> iExistingColorName;
|
|
*pFile >> md_ColorNames[ iExistingColorName];
|
|
}
|
|
|
|
// Read AnimData
|
|
CAnimData::Read_t( pFile);
|
|
IndicesToPtrs();
|
|
|
|
// old models don't have saved polygons per surface
|
|
if( !bHasPolygonsPerSurface)
|
|
{
|
|
// so link them manually
|
|
LinkDataForSurfaces(TRUE);
|
|
|
|
// for each mip model
|
|
for( INDEX iMip = 0; iMip<md_MipCt; iMip ++)
|
|
{
|
|
ModelMipInfo *pMMI = &md_MipInfos[ iMip];
|
|
// for each surface
|
|
for( INDEX iSurface=0; iSurface<pMMI->mmpi_MappingSurfaces.Count(); iSurface++)
|
|
{
|
|
// convert rendering flags into new flags format (per surface)
|
|
if (pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons.Count()>0)
|
|
{
|
|
ULONG ulFlags = pMMI->mmpi_Polygons[pMMI->mmpi_MappingSurfaces[iSurface].ms_aiPolygons[0]].mp_RenderFlags;
|
|
pMMI->mmpi_MappingSurfaces[iSurface].SetRenderingParameters(ulFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// turn on diffuse map for all models of old format
|
|
if( !bHasColorForReflectionAndSpecularity)
|
|
{
|
|
for( INDEX iMip = 0; iMip<md_MipCt; iMip ++)
|
|
{
|
|
ModelMipInfo *pMMI = &md_MipInfos[ iMip];
|
|
for( INDEX iSurface=0; iSurface<pMMI->mmpi_MappingSurfaces.Count(); iSurface++)
|
|
{
|
|
pMMI->mmpi_MappingSurfaces[iSurface].ms_ulRenderingFlags |= SRF_DIFFUSE|SRF_NEW_TEXTURE_FORMAT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bHasDiffuseColor)
|
|
{
|
|
*pFile >> md_colDiffuse;
|
|
}
|
|
|
|
// old models don't have saved colors for reflection and specularity
|
|
if( bHasColorForReflectionAndSpecularity)
|
|
{
|
|
// load colors for reflections, specularity and bump
|
|
*pFile >> md_colReflections;
|
|
*pFile >> md_colSpecular;
|
|
*pFile >> md_colBump;
|
|
}
|
|
|
|
md_bHasAlpha = _bHasAlpha;
|
|
|
|
// precalculate rendering data
|
|
extern void PrepareModelForRendering(CModelData &md);
|
|
PrepareModelForRendering(*this);
|
|
}
|
|
|
|
|
|
// reference counting (override from CAnimData)
|
|
void CModelData::RemReference_internal(void)
|
|
{
|
|
// if this model is part of edit model object
|
|
if (md_bIsEdited) {
|
|
// just unreference it
|
|
MarkUnused();
|
|
// if this model is part of model stock
|
|
} else {
|
|
// release it
|
|
_pModelStock->Release(this);
|
|
}
|
|
}
|
|
|
|
|
|
/* Get the description of this object. */
|
|
CTString CModelData::GetDescription(void)
|
|
{
|
|
CTString str;
|
|
|
|
ModelMipInfo &mmi0 = md_MipInfos[0];
|
|
str.PrintF("%d mips, %d anims, %d frames, %d vtx, %d svx, %d tri",
|
|
md_MipCt, ad_NumberOfAnims, md_FramesCt,
|
|
mmi0.mmpi_ctMipVx, mmi0.mmpi_ctSrfVx, mmi0.mmpi_ctTriangles);
|
|
|
|
return str;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
void ModelMipInfo::Clear()
|
|
{
|
|
mmpi_PolygonsCt = 0; // reset number of polygons
|
|
mmpi_Polygons.Clear(); // clear static arrays ...
|
|
mmpi_TextureVertices.Clear();
|
|
mmpi_MappingSurfaces.Clear();
|
|
mmpi_aPolygonsPerPatch.Clear();
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
ModelPolygon::ModelPolygon()
|
|
{
|
|
mp_RenderFlags = 0;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
ModelPolygon::~ModelPolygon()
|
|
{
|
|
mp_PolygonVertices.Clear();
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
ModelMipInfo::~ModelMipInfo()
|
|
{
|
|
Clear();
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
MappingSurface::~MappingSurface()
|
|
{
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
MappingSurface::MappingSurface()
|
|
{
|
|
ms_ulRenderingFlags &= ~SRF_SELECTED;
|
|
ms_colDiffuse = C_WHITE|CT_OPAQUE;
|
|
ms_colReflections = C_WHITE|CT_OPAQUE;
|
|
ms_colSpecular = C_WHITE|CT_OPAQUE;
|
|
ms_colBump = C_WHITE|CT_OPAQUE;
|
|
ms_ulOnColor = SC_ALLWAYS_ON;
|
|
ms_ulOffColor = SC_ALLWAYS_OFF;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Object constructor
|
|
*/
|
|
CModelObject::CModelObject()
|
|
{
|
|
mo_colBlendColor = 0xFFFFFFFF;
|
|
mo_ColorMask = 0x7FFFFFFF;
|
|
mo_PatchMask = 0;
|
|
mo_iManualMipLevel = 0;
|
|
mo_AutoMipModeling = TRUE;
|
|
mo_Stretch = FLOAT3D(1,1,1);
|
|
}
|
|
/*
|
|
* Destructor
|
|
*/
|
|
CModelObject::~CModelObject()
|
|
{
|
|
for(INDEX iPatch=0; iPatch<MAX_TEXTUREPATCHES; iPatch++)
|
|
{
|
|
HidePatch( iPatch);
|
|
}
|
|
|
|
RemoveAllAttachmentModels();
|
|
}
|
|
// copy from another object of same class
|
|
void CModelObject::Copy(CModelObject &moOther)
|
|
{
|
|
CAnimObject::Copy(moOther);
|
|
|
|
mo_PatchMask = moOther.mo_PatchMask ;
|
|
mo_iManualMipLevel = moOther.mo_iManualMipLevel;
|
|
mo_AutoMipModeling = moOther.mo_AutoMipModeling;
|
|
mo_Stretch = moOther.mo_Stretch ;
|
|
mo_ColorMask = moOther.mo_ColorMask ;
|
|
mo_iLastRenderMipLevel = moOther.mo_iLastRenderMipLevel;
|
|
mo_colBlendColor = moOther.mo_colBlendColor ;
|
|
|
|
mo_toTexture .Copy(moOther.mo_toTexture );
|
|
mo_toReflection .Copy(moOther.mo_toReflection );
|
|
mo_toSpecular .Copy(moOther.mo_toSpecular );
|
|
mo_toBump .Copy(moOther.mo_toBump );
|
|
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, moOther.mo_lhAttachments, itamo) {
|
|
CAttachmentModelObject &amoOther = *itamo;
|
|
CAttachmentModelObject &amo = *AddAttachmentModel(amoOther.amo_iAttachedPosition);
|
|
amo.amo_plRelative = amoOther.amo_plRelative;
|
|
amo.amo_moModelObject.Copy(amoOther.amo_moModelObject);
|
|
}
|
|
}
|
|
|
|
// synchronize with another model (copy animations/attachments positions etc from there)
|
|
void CModelObject::Synchronize(CModelObject &moOther)
|
|
{
|
|
// synchronize animation
|
|
CAnimObject::Synchronize(moOther);
|
|
|
|
// synchronize misc parameters
|
|
mo_PatchMask = moOther.mo_PatchMask ;
|
|
mo_Stretch = moOther.mo_Stretch ;
|
|
mo_ColorMask = moOther.mo_ColorMask ;
|
|
mo_colBlendColor = moOther.mo_colBlendColor ;
|
|
|
|
CModelData *pmd = GetData();
|
|
CModelData *pmdOther = moOther.GetData();
|
|
if (pmd==NULL || pmdOther==NULL) {
|
|
return;
|
|
}
|
|
|
|
// for each attachment in another object
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, moOther.mo_lhAttachments, itamo) {
|
|
CAttachmentModelObject *pamoOther = itamo;
|
|
INDEX iap = pamoOther ->amo_iAttachedPosition;
|
|
// get one here with same index
|
|
CAttachmentModelObject *pamo = GetAttachmentModel(iap);
|
|
// if found
|
|
if (pamo!=NULL) {
|
|
|
|
// sync the model itself
|
|
pamo->amo_moModelObject.Synchronize(pamoOther->amo_moModelObject);
|
|
|
|
// get original placements of both attachments
|
|
pmd->md_aampAttachedPosition.Lock();
|
|
pmdOther->md_aampAttachedPosition.Lock();
|
|
CPlacement3D plOrg = pmd->md_aampAttachedPosition[iap].amp_plRelativePlacement;
|
|
CPlacement3D plOtherOrg = pmdOther->md_aampAttachedPosition[iap].amp_plRelativePlacement;
|
|
pmd->md_aampAttachedPosition.Unlock();
|
|
pmdOther->md_aampAttachedPosition.Unlock();
|
|
FLOAT3D &v2 = pamo->amo_plRelative.pl_PositionVector;
|
|
FLOAT3D &v1 = pamoOther->amo_plRelative.pl_PositionVector;
|
|
FLOAT3D &v2O = plOrg.pl_PositionVector;
|
|
FLOAT3D &v1O = plOtherOrg.pl_PositionVector;
|
|
ANGLE3D &a2 = pamo->amo_plRelative.pl_OrientationAngle;
|
|
ANGLE3D &a1 = pamoOther->amo_plRelative.pl_OrientationAngle;
|
|
ANGLE3D &a2O = plOrg.pl_OrientationAngle;
|
|
ANGLE3D &a1O = plOtherOrg.pl_OrientationAngle;
|
|
v2 = v2O+v1-v1O;
|
|
a2 = a2O+a1-a1O;
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
//------------------------------------------ WRITE
|
|
void CModelObject::Write_t( CTStream *pFile) // throw char *
|
|
{
|
|
CAnimObject::Write_t( pFile);
|
|
|
|
pFile->WriteID_t( CChunkID( "MODT"));
|
|
*pFile << mo_colBlendColor;
|
|
pFile->Write_t( &mo_PatchMask, sizeof(ULONG));
|
|
pFile->Write_t( &mo_Stretch, sizeof(FLOAT3D));
|
|
pFile->Write_t( &mo_ColorMask, sizeof(ULONG));
|
|
}
|
|
//------------------------------------------ READ
|
|
void CModelObject::Read_t( CTStream *pFile) // throw char *
|
|
{
|
|
CAnimObject::Read_t( pFile);
|
|
|
|
if( pFile->PeekID_t()==CChunkID("MODT"))
|
|
{
|
|
pFile->ExpectID_t( CChunkID( "MODT"));
|
|
*pFile >> mo_colBlendColor;
|
|
}
|
|
// model object is saved without dynamic blend color
|
|
else
|
|
{
|
|
mo_colBlendColor = 0xFFFFFFFF;
|
|
}
|
|
|
|
(*pFile)>>mo_PatchMask;
|
|
(*pFile)>>mo_Stretch;
|
|
(*pFile)>>mo_ColorMask;
|
|
for( INDEX i=0; i<MAX_TEXTUREPATCHES; i++)
|
|
{
|
|
if( (mo_PatchMask & ((1UL) << i)) != 0)
|
|
{
|
|
ShowPatch( i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// retrieves model's texture width
|
|
MEX CModelObject::GetWidth()
|
|
{
|
|
return GetData()->md_Width;
|
|
};
|
|
// retrieves model's texture height
|
|
MEX CModelObject::GetHeight()
|
|
{
|
|
return GetData()->md_Height;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine retrives full model data
|
|
*/
|
|
void CModelObject::GetModelInfo(CModelInfo &miInfo)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
ASSERT( pMD != NULL);
|
|
// copy model data values
|
|
miInfo.mi_VerticesCt = pMD->md_VerticesCt;
|
|
miInfo.mi_FramesCt = pMD->md_FramesCt;
|
|
miInfo.mi_MipCt = pMD->md_MipCt;
|
|
for( INDEX i=0; i<pMD->md_MipCt; i++)
|
|
{
|
|
miInfo.mi_MipInfos[ i].mi_PolygonsCt = pMD->md_MipInfos[ i].mmpi_PolygonsCt;
|
|
|
|
// calculate triangeles
|
|
miInfo.mi_MipInfos[ i].mi_TrianglesCt = 0;
|
|
for( INDEX iPolygon = 0; iPolygon<pMD->md_MipInfos[ i].mmpi_PolygonsCt; iPolygon++)
|
|
{
|
|
miInfo.mi_MipInfos[ i].mi_TrianglesCt +=
|
|
pMD->md_MipInfos[ i].mmpi_Polygons[ iPolygon].mp_PolygonVertices.Count()-2;
|
|
}
|
|
|
|
ULONG ulMipMask = (1L) << i; // working mip model's mask
|
|
INDEX iVertexCt = 0;
|
|
// count vertices that exists in this mip model
|
|
for( INDEX j=0; j<pMD->md_VerticesCt; j++)
|
|
if( pMD->md_VertexMipMask[ j] & ulMipMask)
|
|
iVertexCt ++;
|
|
miInfo.mi_MipInfos[ i].mi_VerticesCt = iVertexCt;
|
|
}
|
|
miInfo.mi_Width = pMD->md_Width;
|
|
miInfo.mi_Height = pMD->md_Height;
|
|
miInfo.mi_Flags = pMD->md_Flags;
|
|
miInfo.mi_ShadowQuality = pMD->md_ShadowQuality;
|
|
miInfo.mi_Stretch = pMD->md_Stretch;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Is model visible for given mip factor
|
|
*/
|
|
BOOL CModelObject::IsModelVisible( FLOAT fMipFactor)
|
|
{
|
|
CModelData *pMD = (CModelData*)GetData();
|
|
ASSERT( pMD != NULL);
|
|
ASSERT( pMD->md_MipCt>0);
|
|
// visible if no mip models or disappearence not allowed
|
|
if( pMD->md_MipCt==0 || mdl_iLODDisappear==0) return TRUE;
|
|
// adjust mip factor in case of dynamic stretch factor
|
|
if( mo_Stretch != FLOAT3D(1,1,1)) {
|
|
fMipFactor -= Log2( Max(mo_Stretch(1),Max(mo_Stretch(2),mo_Stretch(3))));
|
|
}
|
|
// eventually adjusted mip factor with LOD control variables
|
|
if( mdl_iLODDisappear==2) fMipFactor = fMipFactor*mdl_fLODMul +mdl_fLODAdd;
|
|
// return true if mip factor is smaller than last in model's mip switch factors array
|
|
return( fMipFactor < pMD->md_MipSwitchFactors[pMD->md_MipCt-1]);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine retrieves activ mip model's index
|
|
*/
|
|
INDEX CModelObject::GetMipModel( FLOAT fMipFactor)
|
|
{
|
|
CModelData *pMD = (CModelData*)GetData();
|
|
ASSERT( pMD != NULL);
|
|
if( !mo_AutoMipModeling) return mo_iManualMipLevel;
|
|
// calculate current mip model
|
|
INDEX i;
|
|
for( i=0; i<pMD->md_MipCt; i++) {
|
|
if( fMipFactor < pMD->md_MipSwitchFactors[i]) return i;
|
|
}
|
|
return i-1;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* retrieves bounding box of given frame
|
|
*/
|
|
FLOATaabbox3D CModelObject::GetFrameBBox( INDEX iFrameNo)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
ASSERT( pMD != NULL);
|
|
return pMD->md_FrameInfos[ iFrameNo].mfi_Box;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine returns mo_AutoMipModeling flag
|
|
*/
|
|
BOOL CModelObject::IsAutoMipModeling()
|
|
{
|
|
return mo_AutoMipModeling;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Sets mo_AutoMipModeling flag to on
|
|
*/
|
|
void CModelObject::AutoMipModelingOn()
|
|
{
|
|
mo_AutoMipModeling = TRUE;
|
|
MarkChanged();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Sets mo_AutoMipModeling flag to off
|
|
*/
|
|
void CModelObject::AutoMipModelingOff()
|
|
{
|
|
mo_AutoMipModeling = FALSE;
|
|
MarkChanged();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine retrieves current mip level
|
|
*/
|
|
INDEX CModelObject::GetManualMipLevel()
|
|
{
|
|
return mo_iManualMipLevel;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine sets current mip level
|
|
*/
|
|
void CModelObject::SetManualMipLevel(INDEX iNewMipLevel)
|
|
{
|
|
mo_iManualMipLevel = iNewMipLevel;
|
|
MarkChanged();
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Routine sets given mip-level's switch factor
|
|
*/
|
|
void CModelObject::SetMipSwitchFactor(INDEX iMipLevel, float fMipFactor)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
ASSERT( iMipLevel < pMD->md_MipCt);
|
|
pMD->md_MipSwitchFactors[ iMipLevel] = fMipFactor;
|
|
MarkChanged();
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Select one rougher mip model level
|
|
*/
|
|
void CModelObject::NextManualMipLevel()
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
if( mo_iManualMipLevel < pMD->md_MipCt-1)
|
|
{
|
|
mo_iManualMipLevel += 1;
|
|
MarkChanged();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Select one more precize mip model level
|
|
*/
|
|
void CModelObject::PrevManualMipLevel()
|
|
{
|
|
if( mo_iManualMipLevel > 0)
|
|
{
|
|
mo_iManualMipLevel -= 1;
|
|
MarkChanged();
|
|
}
|
|
}
|
|
|
|
|
|
// this function returns current value of patches mask
|
|
ULONG CModelObject::GetPatchesMask()
|
|
{
|
|
return mo_PatchMask;
|
|
};
|
|
// use this function to set new patches combination
|
|
void CModelObject::SetPatchesMask(ULONG new_patches_mask)
|
|
{
|
|
mo_PatchMask = new_patches_mask;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Sets new name to a color with given index
|
|
*/
|
|
void CModelObject::SetColorName( INDEX iColor, CTString &strNewName)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
ASSERT( iColor < MAX_COLOR_NAMES);
|
|
pMD->md_ColorNames[ iColor] = strNewName;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Retrieves name of color with given index
|
|
*/
|
|
CTString CModelObject::GetColorName( INDEX iColor)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
ASSERT( iColor < MAX_COLOR_NAMES);
|
|
return pMD->md_ColorNames[ iColor];
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Retrieves color of given surface
|
|
*/
|
|
COLOR CModelObject::GetSurfaceColor( INDEX iCurrentMip, INDEX iCurrentSurface)
|
|
{
|
|
struct ModelPolygon *pPoly;
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
if( (iCurrentMip>=pMD->md_MipCt) ||
|
|
(iCurrentSurface>=pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces.Count()) )
|
|
{
|
|
return (COLOR) -1; // !!! FIXME COLOR is unsigned, right?
|
|
}
|
|
for( INDEX i=0; i<pMD->md_MipInfos[ iCurrentMip].mmpi_PolygonsCt; i++)
|
|
{
|
|
pPoly = &pMD->md_MipInfos[ iCurrentMip].mmpi_Polygons[ i];
|
|
if( pPoly->mp_Surface == iCurrentSurface)
|
|
{
|
|
return pPoly->mp_ColorAndAlpha;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Changes color of given surface
|
|
*/
|
|
void CModelObject::SetSurfaceColor( INDEX iCurrentMip, INDEX iCurrentSurface,
|
|
COLOR colNewColorAndAlpha)
|
|
{
|
|
struct ModelPolygon *pPoly;
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
if( (iCurrentMip>=pMD->md_MipCt) ||
|
|
(iCurrentSurface>=pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces.Count()) )
|
|
{
|
|
return;
|
|
}
|
|
pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces[iCurrentSurface].ms_colColor = colNewColorAndAlpha;
|
|
for( INDEX i=0; i<pMD->md_MipInfos[ iCurrentMip].mmpi_PolygonsCt; i++)
|
|
{
|
|
pPoly = &pMD->md_MipInfos[ iCurrentMip].mmpi_Polygons[ i];
|
|
if( pPoly->mp_Surface == iCurrentSurface)
|
|
{
|
|
pPoly->mp_ColorAndAlpha = colNewColorAndAlpha;
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Retrieves rendering flags of given surface
|
|
*/
|
|
void CModelObject::GetSurfaceRenderFlags( INDEX iCurrentMip, INDEX iCurrentSurface,
|
|
enum SurfaceShadingType &sstShading, enum SurfaceTranslucencyType &sttTranslucency,
|
|
ULONG &ulRenderingFlags)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
if( (iCurrentMip>=pMD->md_MipCt) ||
|
|
(iCurrentSurface>=pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces.Count()) )
|
|
{
|
|
return;
|
|
}
|
|
MappingSurface *pms = &pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces[iCurrentSurface];
|
|
sstShading = pms->ms_sstShadingType;
|
|
sttTranslucency = pms->ms_sttTranslucencyType;
|
|
ulRenderingFlags = pms->ms_ulRenderingFlags;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Changes rendering of given surface
|
|
*/
|
|
void CModelObject::SetSurfaceRenderFlags( INDEX iCurrentMip, INDEX iCurrentSurface,
|
|
enum SurfaceShadingType sstShading, enum SurfaceTranslucencyType sttTranslucency,
|
|
ULONG ulRenderingFlags)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
if( (iCurrentMip>=pMD->md_MipCt) ||
|
|
(iCurrentSurface>=pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces.Count()) )
|
|
{
|
|
return;
|
|
}
|
|
// convert surface rendering parameters from old polygon flags -- temporary !!!!
|
|
MappingSurface *pms = &pMD->md_MipInfos[ iCurrentMip].mmpi_MappingSurfaces[iCurrentSurface];
|
|
pms->ms_sstShadingType = sstShading;
|
|
pms->ms_sttTranslucencyType = sttTranslucency;
|
|
pms->ms_ulRenderingFlags = ulRenderingFlags;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
void CModelObject::ProjectFrameVertices( CProjection3D *pProjection, INDEX iMipModel)
|
|
{
|
|
FLOAT3D f3dVertex;
|
|
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
pProjection->ObjectHandleL() = pMD->md_vCompressedCenter;
|
|
pProjection->ObjectStretchL() = pMD->md_Stretch;
|
|
// apply dynamic stretch
|
|
pProjection->ObjectStretchL()(1) *= mo_Stretch(1);
|
|
pProjection->ObjectStretchL()(2) *= mo_Stretch(2);
|
|
pProjection->ObjectStretchL()(3) *= mo_Stretch(3);
|
|
pProjection->ObjectFaceForwardL() = pMD->md_Flags & (MF_FACE_FORWARD|MF_HALF_FACE_FORWARD);
|
|
pProjection->ObjectHalfFaceForwardL() = pMD->md_Flags & MF_HALF_FACE_FORWARD;
|
|
pProjection->Prepare();
|
|
|
|
INDEX iCurrentFrame = GetFrame();
|
|
ULONG ulVtxMask = (1L) << iMipModel;
|
|
|
|
if( pMD->md_Flags & MF_COMPRESSED_16BIT)
|
|
{
|
|
ModelFrameVertex16 *pFrame = &pMD->md_FrameVertices16[ iCurrentFrame * pMD->md_VerticesCt];
|
|
for( INDEX i=0; i<pMD->md_VerticesCt; i++)
|
|
{
|
|
if( pMD->md_VertexMipMask[ i] & ulVtxMask)
|
|
{
|
|
f3dVertex(1) = (float) pFrame[ i].mfv_SWPoint(1);
|
|
f3dVertex(2) = (float) pFrame[ i].mfv_SWPoint(2);
|
|
f3dVertex(3) = (float) pFrame[ i].mfv_SWPoint(3);
|
|
pProjection->ProjectCoordinate( f3dVertex, pMD->md_TransformedVertices[ i].tvd_TransformedPoint);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ModelFrameVertex8 *pFrame = &pMD->md_FrameVertices8[ iCurrentFrame * pMD->md_VerticesCt];
|
|
for( INDEX i=0; i<pMD->md_VerticesCt; i++)
|
|
{
|
|
if( pMD->md_VertexMipMask[ i] & ulVtxMask)
|
|
{
|
|
f3dVertex(1) = (float) pFrame[ i].mfv_SBPoint(1);
|
|
f3dVertex(2) = (float) pFrame[ i].mfv_SBPoint(2);
|
|
f3dVertex(3) = (float) pFrame[ i].mfv_SBPoint(3);
|
|
pProjection->ProjectCoordinate( f3dVertex, pMD->md_TransformedVertices[ i].tvd_TransformedPoint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Colorizes surfaces touching given box
|
|
*/
|
|
void CModelObject::ColorizeRegion( CDrawPort *pDP, CProjection3D *pProjection, PIXaabbox2D box,
|
|
INDEX iChoosedColor, BOOL bOnColorMode)
|
|
{
|
|
struct ModelPolygon *pPoly;
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
struct TransformedVertexData *pTransformedVertice;
|
|
PIX pixDPHeight = pDP->GetHeight();
|
|
// project vertices for given mip model
|
|
ProjectFrameVertices( pProjection, mo_iLastRenderMipLevel);
|
|
for( INDEX j=0; j<pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_PolygonsCt; j++)
|
|
{
|
|
pPoly = &pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_Polygons[ j];
|
|
for( INDEX i=0; i<pPoly->mp_PolygonVertices.Count(); i++)
|
|
{
|
|
pTransformedVertice = pPoly->mp_PolygonVertices[ i].mpv_ptvTransformedVertex;
|
|
PIXaabbox2D ptBox = PIXaabbox2D( PIX2D( (SWORD) pTransformedVertice->tvd_TransformedPoint(1),
|
|
pixDPHeight - (SWORD) pTransformedVertice->tvd_TransformedPoint(2)));
|
|
if( !((box & ptBox).IsEmpty()) )
|
|
{
|
|
MappingSurface &ms = pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_MappingSurfaces[ pPoly->mp_Surface];
|
|
if( bOnColorMode)
|
|
{
|
|
//pPoly->mp_OnColor = 1UL << iChoosedColor;
|
|
ms.ms_ulOnColor = 1UL << iChoosedColor;
|
|
}
|
|
else
|
|
{
|
|
//pPoly->mp_OffColor = 1UL << iChoosedColor;
|
|
ms.ms_ulOffColor = 1UL << iChoosedColor;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Colorizes polygons touching given box
|
|
*/
|
|
void CModelObject::ApplySurfaceToPolygonsInRegion( CDrawPort *pDP, CProjection3D *pProjection,
|
|
PIXaabbox2D box, INDEX iSurface, COLOR colSurfaceColor)
|
|
{
|
|
// project vertices for given mip model
|
|
ProjectFrameVertices( pProjection, mo_iLastRenderMipLevel);
|
|
|
|
struct ModelPolygon *pPoly;
|
|
struct TransformedVertexData *pTransformedVertice;
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
PIX pixDPHeight = pDP->GetHeight();
|
|
|
|
for( INDEX j=0; j<pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_PolygonsCt; j++)
|
|
{
|
|
pPoly = &pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_Polygons[ j];
|
|
for( INDEX i=0; i<pPoly->mp_PolygonVertices.Count(); i++)
|
|
{
|
|
pTransformedVertice = pPoly->mp_PolygonVertices[ i].mpv_ptvTransformedVertex;
|
|
PIXaabbox2D ptBox = PIXaabbox2D( PIX2D( (SWORD) pTransformedVertice->tvd_TransformedPoint(1),
|
|
pixDPHeight - (SWORD) pTransformedVertice->tvd_TransformedPoint(2)));
|
|
if( !((box & ptBox).IsEmpty()) )
|
|
{
|
|
pPoly->mp_Surface = iSurface;
|
|
pPoly->mp_ColorAndAlpha = colSurfaceColor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// unpack a vertex
|
|
void CModelObject::UnpackVertex(INDEX iFrame, INDEX iVertex, FLOAT3D &vVertex)
|
|
{
|
|
CModelData *pmd = (CModelData *) GetData();
|
|
// get decompression/stretch factors
|
|
FLOAT3D &vDataStretch = pmd->md_Stretch;
|
|
FLOAT3D &vObjectStretch = mo_Stretch;
|
|
FLOAT3D vStretch;
|
|
vStretch(1) = vDataStretch(1)*vObjectStretch(1);
|
|
vStretch(2) = vDataStretch(2)*vObjectStretch(2);
|
|
vStretch(3) = vDataStretch(3)*vObjectStretch(3);
|
|
FLOAT3D vOffset = pmd->md_vCompressedCenter;
|
|
|
|
if( pmd->md_Flags & MF_COMPRESSED_16BIT)
|
|
{
|
|
struct ModelFrameVertex16 *pFrame16 = &pmd->md_FrameVertices16[iFrame * pmd->md_VerticesCt];
|
|
vVertex(1) = (pFrame16[iVertex].mfv_SWPoint(1)-vOffset(1))*vStretch(1);
|
|
vVertex(2) = (pFrame16[iVertex].mfv_SWPoint(2)-vOffset(2))*vStretch(2);
|
|
vVertex(3) = (pFrame16[iVertex].mfv_SWPoint(3)-vOffset(3))*vStretch(3);
|
|
} else {
|
|
struct ModelFrameVertex8 *pFrame8 = &pmd->md_FrameVertices8[iFrame * pmd->md_VerticesCt];
|
|
vVertex(1) = (pFrame8[iVertex].mfv_SBPoint(1)-vOffset(1))*vStretch(1);
|
|
vVertex(2) = (pFrame8[iVertex].mfv_SBPoint(2)-vOffset(2))*vStretch(2);
|
|
vVertex(3) = (pFrame8[iVertex].mfv_SBPoint(3)-vOffset(3))*vStretch(3);
|
|
}
|
|
}
|
|
|
|
CPlacement3D CModelObject::GetAttachmentPlacement(CAttachmentModelObject &amo)
|
|
{
|
|
// project reference points to view space
|
|
FLOAT3D vCenter, vFront, vUp;
|
|
CModelData *pmd = (CModelData *) GetData();
|
|
pmd->md_aampAttachedPosition.Lock();
|
|
INDEX iPosition = amo.amo_iAttachedPosition;
|
|
INDEX iCenter = pmd->md_aampAttachedPosition[iPosition].amp_iCenterVertex;
|
|
INDEX iFront = pmd->md_aampAttachedPosition[iPosition].amp_iFrontVertex;
|
|
INDEX iUp = pmd->md_aampAttachedPosition[iPosition].amp_iUpVertex;
|
|
INDEX iFrame = GetFrame();
|
|
|
|
UnpackVertex( iFrame, iCenter, vCenter);
|
|
UnpackVertex( iFrame, iFront, vFront);
|
|
UnpackVertex( iFrame, iUp, vUp);
|
|
|
|
// make axis vectors in absolute space
|
|
FLOAT3D &vO = vCenter;
|
|
FLOAT3D vY = vUp-vCenter;
|
|
FLOAT3D vZ = vCenter-vFront;
|
|
FLOAT3D vX = vY*vZ;
|
|
vY = vZ*vX;
|
|
// make a rotation matrix from those vectors
|
|
vX.Normalize();
|
|
vY.Normalize();
|
|
vZ.Normalize();
|
|
FLOATmatrix3D mOrientation;
|
|
mOrientation(1,1) = vX(1); mOrientation(1,2) = vY(1); mOrientation(1,3) = vZ(1);
|
|
mOrientation(2,1) = vX(2); mOrientation(2,2) = vY(2); mOrientation(2,3) = vZ(2);
|
|
mOrientation(3,1) = vX(3); mOrientation(3,2) = vY(3); mOrientation(3,3) = vZ(3);
|
|
|
|
// make reference placement in absolute space
|
|
CPlacement3D plPoints;
|
|
plPoints.pl_PositionVector = vO;
|
|
DecomposeRotationMatrixNoSnap(plPoints.pl_OrientationAngle, mOrientation);
|
|
CPlacement3D pl = amo.amo_plRelative;
|
|
pl.RelativeToAbsoluteSmooth(plPoints);
|
|
pmd->md_aampAttachedPosition.Unlock();
|
|
return pl;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Find hitted polygon
|
|
*/
|
|
struct ModelPolygon *CModelObject::PolygonHit(
|
|
CPlacement3D plRay, CPlacement3D plObject, INDEX iCurrentMip, FLOAT &fHitDistance)
|
|
{
|
|
struct ModelPolygon *pResultPoly = NULL;
|
|
|
|
fHitDistance = 100000.0f;
|
|
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo) {
|
|
FLOAT fHit;
|
|
CPlacement3D plAttachment = GetAttachmentPlacement(*itamo);
|
|
plAttachment.RelativeToAbsolute(plObject);
|
|
struct ModelPolygon *pmp = itamo->amo_moModelObject.PolygonHit(plRay, plAttachment, iCurrentMip, fHit);
|
|
if (fHit < fHitDistance) {
|
|
fHitDistance = fHit;
|
|
pResultPoly = pmp;
|
|
}
|
|
}
|
|
FLOAT fHit;
|
|
struct ModelPolygon *pmp = PolygonHitModelData((CModelData*)GetData(), plRay, plObject, iCurrentMip, fHit);
|
|
if (fHit < fHitDistance) {
|
|
fHitDistance = fHit;
|
|
pResultPoly = pmp;
|
|
}
|
|
|
|
return pResultPoly;
|
|
}
|
|
|
|
struct ModelPolygon *CModelObject::PolygonHitModelData(CModelData *pMD,
|
|
CPlacement3D plRay, CPlacement3D plObject, INDEX iCurrentMip, FLOAT &fHitDistance)
|
|
{
|
|
FLOAT fClosest = -100000.0f;
|
|
struct ModelPolygon *pPoly, *pResultPoly = NULL;
|
|
CIntersector Intersector;
|
|
|
|
CSimpleProjection3D spProjection;
|
|
spProjection.ViewerPlacementL() = plRay;
|
|
spProjection.ObjectPlacementL() = plObject;
|
|
// project vertices for given mip model
|
|
ProjectFrameVertices( &spProjection, iCurrentMip);
|
|
|
|
for( INDEX j=0; j<pMD->md_MipInfos[ iCurrentMip].mmpi_PolygonsCt; j++)
|
|
{
|
|
Intersector.Clear();
|
|
pPoly = &pMD->md_MipInfos[ iCurrentMip].mmpi_Polygons[ j];
|
|
for( INDEX i=0; i<pPoly->mp_PolygonVertices.Count(); i++)
|
|
{
|
|
// get next vertex index (first is i)
|
|
INDEX next = (i+1) % pPoly->mp_PolygonVertices.Count();
|
|
// add edge to intersection object
|
|
Intersector.AddEdge( pPoly->mp_PolygonVertices[ i].mpv_ptvTransformedVertex->tvd_TransformedPoint(1),
|
|
pPoly->mp_PolygonVertices[ i].mpv_ptvTransformedVertex->tvd_TransformedPoint(2),
|
|
pPoly->mp_PolygonVertices[ next].mpv_ptvTransformedVertex->tvd_TransformedPoint(1),
|
|
pPoly->mp_PolygonVertices[ next].mpv_ptvTransformedVertex->tvd_TransformedPoint(2));
|
|
}
|
|
if( Intersector.IsIntersecting())
|
|
{
|
|
FLOAT3D f3dTr0 = pPoly->mp_PolygonVertices[ 0].mpv_ptvTransformedVertex->tvd_TransformedPoint;
|
|
FLOAT3D f3dTr1 = pPoly->mp_PolygonVertices[ 1].mpv_ptvTransformedVertex->tvd_TransformedPoint;
|
|
FLOAT3D f3dTr2 = pPoly->mp_PolygonVertices[ 2].mpv_ptvTransformedVertex->tvd_TransformedPoint;
|
|
FLOATplane3D fplPlane = FLOATplane3D( f3dTr0, f3dTr1, f3dTr2);
|
|
|
|
FLOAT3D f3dHitted3DPoint = FLOAT3D(0,0,0);
|
|
fplPlane.GetCoordinate( 3, f3dHitted3DPoint);
|
|
if( f3dHitted3DPoint(3)<=0.0f && f3dHitted3DPoint(3)> fClosest)
|
|
{
|
|
fClosest = f3dHitted3DPoint(3);
|
|
pResultPoly = pPoly;
|
|
}
|
|
}
|
|
}
|
|
// return closest hit polygon and the distance where it was hit
|
|
fHitDistance = -fClosest;
|
|
return pResultPoly;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Colorizes hitted polygon
|
|
*/
|
|
void CModelObject::ColorizePolygon( CDrawPort *pDP, CProjection3D *projection, PIX x1, PIX y1,
|
|
INDEX iChoosedColor, BOOL bOnColorMode)
|
|
{
|
|
CPlacement3D plRay;
|
|
CPlacement3D plObjectPlacement;
|
|
|
|
projection->Prepare();
|
|
projection->RayThroughPoint( FLOAT3D( (FLOAT)x1, (FLOAT)(pDP->GetHeight()-y1), 0.0f),
|
|
plRay);
|
|
plObjectPlacement = projection->ObjectPlacementR();
|
|
|
|
FLOAT fHitDistance;
|
|
struct ModelPolygon *pPoly = PolygonHit( plRay, plObjectPlacement, mo_iLastRenderMipLevel, fHitDistance);
|
|
if( pPoly != NULL)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
MappingSurface &ms = pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_MappingSurfaces[ pPoly->mp_Surface];
|
|
if( bOnColorMode)
|
|
{
|
|
//pPoly->mp_OnColor = 1UL << iChoosedColor;
|
|
ms.ms_ulOnColor = 1UL << iChoosedColor;
|
|
}
|
|
else
|
|
{
|
|
//pPoly->mp_OffColor = 1UL << iChoosedColor;
|
|
ms.ms_ulOffColor = 1UL << iChoosedColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModelObject::ApplySurfaceToPolygon( CDrawPort *pDP, CProjection3D *projection,
|
|
PIX x1, PIX y1, INDEX iSurface, COLOR colSurfaceColor)
|
|
{
|
|
CPlacement3D plRay;
|
|
CPlacement3D plObjectPlacement;
|
|
|
|
projection->Prepare();
|
|
projection->RayThroughPoint( FLOAT3D( (FLOAT)x1, (FLOAT)(pDP->GetHeight()-y1), 0.0f),
|
|
plRay);
|
|
plObjectPlacement = projection->ObjectPlacementR();
|
|
|
|
FLOAT fHitDistance;
|
|
struct ModelPolygon *pPoly = PolygonHit( plRay, plObjectPlacement, mo_iLastRenderMipLevel, fHitDistance);
|
|
if( pPoly != NULL)
|
|
{
|
|
pPoly->mp_ColorAndAlpha = colSurfaceColor;
|
|
pPoly->mp_Surface = iSurface;
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Picks color from hitted polygon
|
|
*/
|
|
void CModelObject::PickPolyColor( CDrawPort *pDP, CProjection3D *projection, PIX x1, PIX y1,
|
|
INDEX &iPickedColorNo, BOOL bOnColorMode)
|
|
{
|
|
CPlacement3D plRay;
|
|
CPlacement3D plObjectPlacement;
|
|
|
|
projection->Prepare();
|
|
projection->RayThroughPoint( FLOAT3D( (FLOAT)x1, (FLOAT)(pDP->GetHeight()-y1), 0.0f),
|
|
plRay);
|
|
plObjectPlacement = projection->ObjectPlacementR();
|
|
|
|
FLOAT fHitDistance;
|
|
struct ModelPolygon *pPoly = PolygonHit( plRay, plObjectPlacement, mo_iLastRenderMipLevel, fHitDistance);
|
|
if( pPoly != NULL)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
MappingSurface &ms = pMD->md_MipInfos[ mo_iLastRenderMipLevel].mmpi_MappingSurfaces[ pPoly->mp_Surface];
|
|
|
|
if( bOnColorMode)
|
|
{
|
|
iPickedColorNo = GetBit( ms.ms_ulOnColor);
|
|
}
|
|
else
|
|
{
|
|
iPickedColorNo = GetBit( ms.ms_ulOffColor);
|
|
}
|
|
}
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
INDEX CModelObject::PickPolySurface( CDrawPort *pDP, CProjection3D *projection, PIX x1, PIX y1)
|
|
{
|
|
CPlacement3D plRay;
|
|
CPlacement3D plObjectPlacement;
|
|
|
|
projection->Prepare();
|
|
projection->RayThroughPoint( FLOAT3D( (FLOAT)x1, (FLOAT)(pDP->GetHeight()-y1), 0.0f),
|
|
plRay);
|
|
plObjectPlacement = projection->ObjectPlacementR();
|
|
|
|
FLOAT fHitDistance;
|
|
struct ModelPolygon *pPoly = PolygonHit( plRay, plObjectPlacement, mo_iLastRenderMipLevel, fHitDistance);
|
|
if( pPoly != NULL)
|
|
{
|
|
return pPoly->mp_Surface;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Obtains index of closest vertex
|
|
*/
|
|
INDEX CModelObject::PickVertexIndex( CDrawPort *pDP, CProjection3D *pProjection, PIX x1, PIX y1,
|
|
FLOAT3D &vClosestVertex)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
// project vertices for given mip model
|
|
ProjectFrameVertices( pProjection, mo_iLastRenderMipLevel);
|
|
|
|
FLOAT fClosest = 64.0f;
|
|
FLOAT iClosest = -1;
|
|
INDEX iCurrentFrame = GetFrame();
|
|
FLOAT3D vTargetPoint = FLOAT3D( x1, pDP->GetHeight()-y1, 0.0f);
|
|
ULONG ulVtxMask = (1L) << mo_iLastRenderMipLevel;
|
|
// Find closest vertice
|
|
for( INDEX iVertex=0; iVertex<pMD->md_VerticesCt; iVertex++)
|
|
{
|
|
if( pMD->md_VertexMipMask[ iVertex] & ulVtxMask)
|
|
{
|
|
FLOAT3D vProjected = pMD->md_TransformedVertices[ iVertex].tvd_TransformedPoint;
|
|
vProjected(3) = 0.0f;
|
|
FLOAT3D vUncompressedVertex;
|
|
if( pMD->md_Flags & MF_COMPRESSED_16BIT)
|
|
{
|
|
ModelFrameVertex16 *pFrame = &pMD->md_FrameVertices16[ iCurrentFrame * pMD->md_VerticesCt];
|
|
vUncompressedVertex(1) = (float) pFrame[ iVertex].mfv_SWPoint(1);
|
|
vUncompressedVertex(2) = (float) pFrame[ iVertex].mfv_SWPoint(2);
|
|
vUncompressedVertex(3) = (float) pFrame[ iVertex].mfv_SWPoint(3);
|
|
}
|
|
else
|
|
{
|
|
ModelFrameVertex8 *pFrame = &pMD->md_FrameVertices8[ iCurrentFrame * pMD->md_VerticesCt];
|
|
vUncompressedVertex(1) = (float) pFrame[ iVertex].mfv_SBPoint(1);
|
|
vUncompressedVertex(2) = (float) pFrame[ iVertex].mfv_SBPoint(2);
|
|
vUncompressedVertex(3) = (float) pFrame[ iVertex].mfv_SBPoint(3);
|
|
}
|
|
|
|
FLOAT fDistance = Abs( ( vProjected-vTargetPoint).Length());
|
|
if( fDistance < fClosest)
|
|
{
|
|
fClosest = fDistance;
|
|
iClosest = iVertex;
|
|
vClosestVertex(1) = vUncompressedVertex(1)*pMD->md_Stretch(1);
|
|
vClosestVertex(2) = vUncompressedVertex(2)*pMD->md_Stretch(2);
|
|
vClosestVertex(3) = vUncompressedVertex(3)*pMD->md_Stretch(3);
|
|
}
|
|
}
|
|
}
|
|
return ((INDEX) iClosest);
|
|
}
|
|
|
|
/*
|
|
* Retrieves current frame's bounding box
|
|
*/
|
|
void CModelObject::GetCurrentFrameBBox( FLOATaabbox3D &MaxBB)
|
|
{
|
|
// obtain model data ptr
|
|
CModelData *pMD = (CModelData *)GetData();
|
|
ASSERT( pMD != NULL);
|
|
// get current frame
|
|
INDEX iCurrentFrame = GetFrame();
|
|
ASSERT( iCurrentFrame < pMD->md_FramesCt);
|
|
// set current frame's bounding box
|
|
MaxBB = pMD->md_FrameInfos[ iCurrentFrame].mfi_Box;
|
|
}
|
|
|
|
/*
|
|
* Retrieves bounding box of all frames
|
|
*/
|
|
void CModelObject::GetAllFramesBBox( FLOATaabbox3D &MaxBB)
|
|
{
|
|
// obtain model data ptr
|
|
CModelData *pMD = (CModelData *)GetData();
|
|
ASSERT( pMD != NULL);
|
|
// get all frames bounding box
|
|
pMD->GetAllFramesBBox( MaxBB);
|
|
}
|
|
|
|
FLOAT3D CModelObject::GetCollisionBoxMin(INDEX iCollisionBox)
|
|
{
|
|
return GetData()->GetCollisionBoxMin(iCollisionBox);
|
|
}
|
|
|
|
FLOAT3D CModelObject::GetCollisionBoxMax(INDEX iCollisionBox)
|
|
{
|
|
return GetData()->GetCollisionBoxMax(iCollisionBox);
|
|
}
|
|
|
|
// returns HEIGHT_EQ_WIDTH, LENGTH_EQ_WIDTH or LENGTH_EQ_HEIGHT
|
|
INDEX CModelObject::GetCollisionBoxDimensionEquality(INDEX iCollisionBox)
|
|
{
|
|
return GetData()->GetCollisionBoxDimensionEquality(iCollisionBox);
|
|
}
|
|
// test it the model has alpha blending
|
|
BOOL CModelObject::HasAlpha(void)
|
|
{
|
|
return GetData()->md_bHasAlpha || (mo_colBlendColor&0xFF)!=0xFF;
|
|
}
|
|
|
|
// retrieves number of surfaces used in given mip model
|
|
INDEX CModelObject::SurfacesCt(INDEX iMipModel){
|
|
ASSERT( GetData() != NULL);
|
|
return GetData()->md_MipInfos[ iMipModel].mmpi_MappingSurfaces.Count();
|
|
};
|
|
// retrieves number of polygons in given surface in given mip model
|
|
INDEX CModelObject::PolygonsInSurfaceCt(INDEX iMipModel, INDEX iSurface)
|
|
{
|
|
ASSERT( GetData() != NULL);
|
|
return GetData()->md_MipInfos[ iMipModel].mmpi_MappingSurfaces[iSurface].ms_aiPolygons.Count();
|
|
};
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Adds and shows given patch
|
|
*/
|
|
void CModelObject::ShowPatch( INDEX iMaskBit)
|
|
{
|
|
CModelData *pMD = (CModelData *)GetData();
|
|
ASSERT( pMD != NULL);
|
|
if( pMD == NULL) return;
|
|
if( (mo_PatchMask & ((1UL) << iMaskBit)) != 0) return;
|
|
mo_PatchMask |= (1UL) << iMaskBit;
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Hides given patch
|
|
*/
|
|
void CModelObject::HidePatch( INDEX iMaskBit)
|
|
{
|
|
CModelData *pMD = (CModelData *)GetData();
|
|
if( pMD == NULL) return;
|
|
if( (mo_PatchMask & ((1UL) << iMaskBit)) == 0) return;
|
|
mo_PatchMask &= ~((1UL) << iMaskBit);
|
|
}
|
|
//--------------------------------------------------------------------------------------------
|
|
/*
|
|
* Retrieves index of mip model that casts shadow
|
|
*/
|
|
BOOL CModelObject::HasShadow(INDEX iModelMip)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
SLONG slShadowQuality = _mrpModelRenderPrefs.GetShadowQuality();
|
|
ASSERT( slShadowQuality >= 0);
|
|
SLONG res = iModelMip + slShadowQuality + pMD->md_ShadowQuality;
|
|
if( res >= pMD->md_MipCt)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Set texture data for main texture in surface of this model.
|
|
*/
|
|
void CModelObject::SetTextureData(CTextureData *ptdNewMainTexture)
|
|
{
|
|
mo_toTexture.SetData(ptdNewMainTexture);
|
|
}
|
|
|
|
CTFileName CModelObject::GetName(void)
|
|
{
|
|
CModelData *pmd = (CModelData *) GetData();
|
|
if( pmd == NULL) return CTString( "");
|
|
return pmd->GetName();
|
|
}
|
|
|
|
// obtain model and set it for this object
|
|
void CModelObject::SetData_t(const CTFileName &fnmModel) // throw char *
|
|
{
|
|
// if the filename is empty
|
|
if (fnmModel=="") {
|
|
// release current texture
|
|
SetData(NULL);
|
|
|
|
// if the filename is not empty
|
|
} else {
|
|
// obtain it (adds one reference)
|
|
CModelData *pmd = _pModelStock->Obtain_t(fnmModel);
|
|
// set it as data (adds one more reference, and remove old reference)
|
|
SetData(pmd);
|
|
// release it (removes one reference)
|
|
_pModelStock->Release(pmd);
|
|
// total reference count +1+1-1 = +1 for new data -1 for old data
|
|
}
|
|
}
|
|
|
|
void CModelObject::SetData(CModelData *pmd)
|
|
{
|
|
RemoveAllAttachmentModels();
|
|
CAnimObject::SetData(pmd);
|
|
}
|
|
|
|
CModelData *CModelObject::GetData(void)
|
|
{
|
|
return (CModelData*)CAnimObject::GetData();
|
|
}
|
|
|
|
void CModelObject::AutoSetTextures(void)
|
|
{
|
|
CTFileName fnModel = GetName();
|
|
CTFileName fnDiffuse;
|
|
INDEX ctDiffuseTextures;
|
|
CTFileName fnReflection;
|
|
CTFileName fnSpecular;
|
|
CTFileName fnBump;
|
|
// extract from model's ini file informations about attachment model's textures
|
|
try
|
|
{
|
|
CTFileName fnIni = fnModel.NoExt()+".ini";
|
|
CTFileStream strmIni;
|
|
strmIni.Open_t( fnIni);
|
|
SLONG slFileSize = strmIni.GetStreamSize();
|
|
// NEVER!NEVER! read after EOF
|
|
while(strmIni.GetPos_t()<(slFileSize-4))
|
|
{
|
|
CChunkID id = strmIni.PeekID_t();
|
|
if( id == CChunkID("WTEX"))
|
|
{
|
|
CChunkID idDummy = strmIni.GetID_t();
|
|
strmIni >> ctDiffuseTextures;
|
|
strmIni >> fnDiffuse;
|
|
}
|
|
else if( id == CChunkID("FXTR"))
|
|
{
|
|
CChunkID idDummy = strmIni.GetID_t();
|
|
strmIni >> fnReflection;
|
|
}
|
|
else if( id == CChunkID("FXTS"))
|
|
{
|
|
CChunkID idDummy = strmIni.GetID_t();
|
|
strmIni >> fnSpecular;
|
|
}
|
|
else if( id == CChunkID("FXTB"))
|
|
{
|
|
CChunkID idDummy = strmIni.GetID_t();
|
|
strmIni >> fnBump;
|
|
}
|
|
else
|
|
{
|
|
strmIni.Seek_t(1,CTStream::SD_CUR);
|
|
}
|
|
}
|
|
}
|
|
catch( char *strError){ (void) strError;}
|
|
|
|
try
|
|
{
|
|
if( fnDiffuse != "") mo_toTexture.SetData_t( fnDiffuse);
|
|
if( fnReflection != "") mo_toReflection.SetData_t( fnReflection);
|
|
if( fnSpecular != "") mo_toSpecular.SetData_t( fnSpecular);
|
|
if( fnBump != "") mo_toBump.SetData_t( fnBump);
|
|
}
|
|
catch( char *strError){ (void) strError;}
|
|
}
|
|
|
|
void CModelObject::AutoSetAttachments(void)
|
|
{
|
|
CTFileName fnModel = GetName();
|
|
RemoveAllAttachmentModels();
|
|
|
|
// extract from model's ini file informations about attachment model's textures
|
|
try
|
|
{
|
|
CTFileName fnIni = fnModel.NoExt()+".ini";
|
|
CTFileStream strmIni;
|
|
strmIni.Open_t( fnIni);
|
|
SLONG slFileSize = strmIni.GetStreamSize();
|
|
// NEVER!NEVER! read after EOF
|
|
while(strmIni.GetPos_t()<(slFileSize-4))
|
|
{
|
|
CChunkID id = strmIni.PeekID_t();
|
|
if( id == CChunkID("ATTM"))
|
|
{
|
|
CChunkID idDummy = strmIni.GetID_t();
|
|
// try to load attached models
|
|
INDEX ctAttachedModels;
|
|
strmIni >> ctAttachedModels;
|
|
// read all attached models
|
|
for( INDEX iAtt=0; iAtt<ctAttachedModels; iAtt++)
|
|
{
|
|
BOOL bVisible;
|
|
CTString strName;
|
|
CTFileName fnModel, fnDummy;
|
|
strmIni >> bVisible;
|
|
strmIni >> strName;
|
|
// this data is used no more
|
|
strmIni >> fnModel;
|
|
|
|
INDEX iAnimation = 0;
|
|
// new attached model format has saved index of animation
|
|
if( strmIni.PeekID_t() == CChunkID("AMAN"))
|
|
{
|
|
strmIni.ExpectID_t( CChunkID( "AMAN"));
|
|
strmIni >> iAnimation;
|
|
}
|
|
else
|
|
{
|
|
strmIni >> fnDummy; // ex model's texture
|
|
}
|
|
|
|
if( bVisible)
|
|
{
|
|
CAttachmentModelObject *pamo = AddAttachmentModel( iAtt);
|
|
pamo->amo_moModelObject.SetData_t( fnModel);
|
|
pamo->amo_moModelObject.AutoSetTextures();
|
|
pamo->amo_moModelObject.StartAnim( iAnimation);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strmIni.Seek_t(1,CTStream::SD_CUR);
|
|
}
|
|
}
|
|
}
|
|
catch( char *strError)
|
|
{
|
|
(void) strError;
|
|
RemoveAllAttachmentModels();
|
|
}
|
|
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo)
|
|
{
|
|
itamo->amo_moModelObject.AutoSetAttachments();
|
|
}
|
|
}
|
|
|
|
CAttachmentModelObject *CModelObject::AddAttachmentModel( INDEX iAttachedPosition)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
|
|
if (pMD->md_aampAttachedPosition.Count()==0) {
|
|
return NULL;
|
|
}
|
|
ASSERT( iAttachedPosition >= 0);
|
|
ASSERT( iAttachedPosition < pMD->md_aampAttachedPosition.Count());
|
|
iAttachedPosition = Clamp(iAttachedPosition, INDEX(0), INDEX(pMD->md_aampAttachedPosition.Count()-1));
|
|
CAttachmentModelObject *pamoNew = new CAttachmentModelObject;
|
|
mo_lhAttachments.AddTail( pamoNew->amo_lnInMain);
|
|
pamoNew->amo_iAttachedPosition = iAttachedPosition;
|
|
pMD->md_aampAttachedPosition.Lock();
|
|
pamoNew->amo_plRelative = pMD->md_aampAttachedPosition[iAttachedPosition].amp_plRelativePlacement;
|
|
pMD->md_aampAttachedPosition.Unlock();
|
|
|
|
return pamoNew;
|
|
}
|
|
|
|
CAttachmentModelObject *CModelObject::GetAttachmentModel( INDEX ipos)
|
|
{
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo) {
|
|
CAttachmentModelObject &amo = *itamo;
|
|
if (amo.amo_iAttachedPosition == ipos) {
|
|
return &amo;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
CAttachmentModelObject *CModelObject::GetAttachmentModelList( INDEX ipos, ...)
|
|
{
|
|
va_list marker;
|
|
va_start(marker, ipos);
|
|
|
|
CAttachmentModelObject *pamo = NULL;
|
|
CModelObject *pmo = this;
|
|
|
|
// while not end of list
|
|
while(ipos>=0) {
|
|
// get attachment
|
|
pamo = pmo->GetAttachmentModel(ipos);
|
|
// if not found
|
|
if (pamo==NULL) {
|
|
// return failure
|
|
va_end(marker);
|
|
return NULL;
|
|
}
|
|
// get next attachment in list
|
|
pmo = &pamo->amo_moModelObject;
|
|
ipos = va_arg( marker, INDEX);
|
|
}
|
|
va_end(marker);
|
|
|
|
// return current attachment
|
|
ASSERT(pamo!=NULL);
|
|
return pamo;
|
|
}
|
|
|
|
void CModelObject::ResetAttachmentModelPosition( INDEX iAttachedPosition)
|
|
{
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo)
|
|
{
|
|
if (itamo->amo_iAttachedPosition == iAttachedPosition)
|
|
{
|
|
CModelData *pMD = (CModelData *) GetData();
|
|
pMD->md_aampAttachedPosition.Lock();
|
|
itamo->amo_plRelative = pMD->md_aampAttachedPosition[iAttachedPosition].amp_plRelativePlacement;
|
|
pMD->md_aampAttachedPosition.Unlock();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModelObject::RemoveAttachmentModel( INDEX iAttachedPosition)
|
|
{
|
|
FORDELETELIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo) {
|
|
if (itamo->amo_iAttachedPosition == iAttachedPosition) {
|
|
itamo->amo_lnInMain.Remove();
|
|
delete &*itamo;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModelObject::RemoveAllAttachmentModels(void)
|
|
{
|
|
FORDELETELIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo) {
|
|
itamo->amo_lnInMain.Remove();
|
|
delete &*itamo;
|
|
}
|
|
}
|
|
|
|
void CModelObject::StretchModel(const FLOAT3D &vStretch)
|
|
{
|
|
mo_Stretch = vStretch;
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo) {
|
|
itamo->amo_moModelObject.StretchModel(vStretch);
|
|
}
|
|
}
|
|
|
|
void CModelObject::StretchModelRelative(const FLOAT3D &vStretch)
|
|
{
|
|
mo_Stretch(1) *= vStretch(1);
|
|
mo_Stretch(2) *= vStretch(2);
|
|
mo_Stretch(3) *= vStretch(3);
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itamo) {
|
|
itamo->amo_moModelObject.StretchModelRelative(vStretch);
|
|
}
|
|
}
|
|
|
|
void CModelObject::StretchSingleModel(const FLOAT3D &vStretch)
|
|
{
|
|
mo_Stretch = vStretch;
|
|
}
|
|
|
|
|
|
// get amount of memory used by this object
|
|
SLONG CModelObject::GetUsedMemory(void)
|
|
{
|
|
// initial size
|
|
SLONG slUsedMemory = sizeof(CModelObject);
|
|
// add attachment(s) size
|
|
FOREACHINLIST( CAttachmentModelObject, amo_lnInMain, mo_lhAttachments, itat) {
|
|
slUsedMemory += sizeof(CAttachmentModelObject) - sizeof(CModelObject);
|
|
itat->amo_moModelObject.GetUsedMemory();
|
|
}
|
|
// done
|
|
return slUsedMemory;
|
|
}
|