MFString MFString::SubStr(int offset, int count) const { if(!pData) return *this; // limit within the strings range int maxChars = (int)pData->bytes - offset; if(count < 0 || count > maxChars) count = maxChars; // bail if we don't need to do anything if(count == (int)pData->bytes) return *this; // allocate a new string MFString t; t.Reserve(count + 1); t.pData->bytes = count; // copy sub string MFString_CopyN(t.pData->pMemory, pData->pMemory + offset, count); t.pData->pMemory[count] = 0; return t; }
void dBTextProp::SetJustification(dBEntity *pEntity, dBRuntimeArgs *pArguments) { dBTextProp *pTextProp = (dBTextProp*)pEntity; MFString arg = pArguments->GetString(0); const char *pStr = arg.CStr(); if(!MFString_CaseCmp(pStr, "top_left")) pTextProp->justification = MFFontJustify_Top_Left; else if(!MFString_CaseCmp(pStr, "top_center")) pTextProp->justification = MFFontJustify_Top_Center; else if(!MFString_CaseCmp(pStr, "top_right")) pTextProp->justification = MFFontJustify_Top_Right; else if(!MFString_CaseCmp(pStr, "top_full")) pTextProp->justification = MFFontJustify_Top_Full; else if(!MFString_CaseCmp(pStr, "center_left")) pTextProp->justification = MFFontJustify_Center_Left; else if(!MFString_CaseCmp(pStr, "center")) pTextProp->justification = MFFontJustify_Center; else if(!MFString_CaseCmp(pStr, "center_right")) pTextProp->justification = MFFontJustify_Center_Right; else if(!MFString_CaseCmp(pStr, "center_full")) pTextProp->justification = MFFontJustify_Center_Full; else if(!MFString_CaseCmp(pStr, "bottom_Left")) pTextProp->justification = MFFontJustify_Bottom_Left; else if(!MFString_CaseCmp(pStr, "bottom_center")) pTextProp->justification = MFFontJustify_Bottom_Center; else if(!MFString_CaseCmp(pStr, "bottom_right")) pTextProp->justification = MFFontJustify_Bottom_Right; else if(!MFString_CaseCmp(pStr, "bottom_full")) pTextProp->justification = MFFontJustify_Bottom_Full; }
MF_API MFString MFString_GetStats() { size_t overhead = stringPool.GetTotalMemory() + stringPool.GetOverheadMemory() + stringHeap.GetOverheadMemory(); size_t waste = 0, averageSize = 0; // calculate waste int numStrings = stringPool.GetNumAllocated(); for(int a=0; a<numStrings; ++a) { MFStringData *pString = (MFStringData*)stringPool.GetItem(a); size_t allocated = pString->allocated; if(allocated) { size_t bytes = pString->bytes+1; averageSize += bytes; waste += allocated - bytes; } } if(numStrings) averageSize /= numStrings; MFString desc; desc.Reserve(1024); desc = MFStr("String heap memory: " MFFMT_SIZE_T " allocated/" MFFMT_SIZE_T " reserved + " MFFMT_SIZE_T " overhead Waste: " MFFMT_SIZE_T " Average length: " MFFMT_SIZE_T "\n\tPool: %d/%d", stringHeap.GetAllocatedMemory(), stringHeap.GetTotalMemory(), overhead, waste, averageSize, stringPool.GetNumAllocated(), stringPool.GetNumReserved()); int numGroups = stringHeap.GetNumPools(); for(int a=0; a<numGroups; ++a) { MFObjectPool *pPool = stringHeap.GetPool(a); desc += MFStr("\n\t\t" MFFMT_SIZE_T " byte: %d/%d", pPool->GetObjectSize(), pPool->GetNumAllocated(), pPool->GetNumReserved()); } return desc; }
static void MissingStates(MFStateBlockConstantType type, uint32 missing) { static const char * const sStateType[MFSB_CT_TypeCount] = { "Bool", "Vector", "Matrix", "RenderState", "Texture", "Misc", "", "Unknown" }; MFString states; for(int a=0; a<32; ++a) { if(missing & (1<<a)) { if(!states.IsNull()) states += ", "; states += MFStateBlock_GetRenderStateName(type, a); } } MFDebug_Assert(missing == 0, MFStr("Missing %s states: %s", sStateType[type], states.CStr())); }
static int MFFileSystem_GetNumEntries(const char *pFindPattern, bool recursive, bool flatten, size_t *pStringLengths) { MFFindData findData; MFFind *hFind; int numFiles = 0; hFind = MFFileSystem_FindFirst(MFString::Format("%s*", pFindPattern).CStr(), &findData); if(hFind) { *pStringLengths += MFString_Length(findData.pSystemPath) + 1; } while(hFind) { if(MFString_Compare(findData.pFilename, ".") && MFString_Compare(findData.pFilename, "..") && MFString_Compare(findData.pFilename, ".svn")) { if(findData.info.attributes & MFFA_Directory) { if(recursive) { if(flatten) { MFString newPath = MFString::Format("%s%s/", pFindPattern, findData.pFilename); numFiles += MFFileSystem_GetNumEntries(newPath.CStr(), recursive, flatten, pStringLengths); } else { *pStringLengths += MFString_Length(findData.pFilename) + 1; ++numFiles; } } } else { *pStringLengths += MFString_Length(findData.pFilename) + 1; ++numFiles; } } if(!MFFileSystem_FindNext(hFind, &findData)) { MFFileSystem_FindClose(hFind); hFind = NULL; } } return numFiles; }
MFString MFString::Lower() const { if(!pData) return *this; // allocate a new string MFString t; t.Reserve(pData->bytes + 1); t.pData->bytes = pData->bytes; // copy lower case string for(size_t a=0; a<pData->bytes + 1; ++a) t.pData->pMemory[a] = (char)MFToLower(pData->pMemory[a]); return t; }
MFString MFString::operator+(const MFString &string) const { if(string.IsEmpty()) return *this; if(IsEmpty()) return string; size_t bytes = pData->bytes + string.pData->bytes; MFString t; t.Reserve(bytes + 1); MFString_CopyCat(t.pData->pMemory, pData->pMemory, string.pData->pMemory); t.pData->bytes = bytes; return t; }
MF_API void MFString_Dump() { MFString temp = MFString_GetStats(); MFDebug_Log(1, "\n-------------------------------------------------------------------------------------------------------"); MFDebug_Log(1, temp.CStr()); // dump all strings... MFDebug_Log(1, ""); int numStrings = stringPool.GetNumAllocated(); for(int a=0; a<numStrings; ++a) { MFStringData *pString = (MFStringData*)stringPool.GetItem(a); MFDebug_Log(1, MFStr("%d refs, " MFFMT_SIZE_T "b: \"%s\"", pString->refCount, pString->allocated, pString->pMemory)); } }
MFString MFString::operator+(const char *pString) const { if(!pString || *pString == 0) return *this; if(IsEmpty()) return MFString(pString); size_t bytes = pData->bytes + MFString_Length(pString); MFString t; t.Reserve(bytes + 1); MFString_CopyCat(t.pData->pMemory, pData->pMemory, pString); t.pData->bytes = bytes; return t; }
void X3DConverter::startGroup(const std::string& name, const Vector3F& translation, const int& materialId) { // Ensuring the name is compatible with the DEF attribute string x3dName = name; size_t p; while ((p = x3dName.find_first_of(' ')) != string::npos) x3dName[p] = '_'; while (x3dName[0] == '-') x3dName[0] = '_'; while ((p = x3dName.find_first_of('/')) != string::npos) { x3dName[p] = '_'; } m_materials.push_back(materialId); m_groups.push_back(name); if (m_split) { startNode(ID::Inline); MFString name; name.push_back(x3dName + ".x3d"); m_writers.back()->setMFString(ID::url, name); X3DWriter* writer = m_binary ? (X3DWriter*)new X3DWriterFI() : (X3DWriter*)new X3DWriterXML(); writer->setProperty(Property::IntEncodingAlgorithm, (void*)Encoder::DeltazlibIntArrayEncoder); writer->setProperty(Property::FloatEncodingAlgorithm, (void*)Encoder::QuantizedzlibFloatArrayEncoder); writer->openFile((x3dName + ".x3d").data()); m_writers.push_back(writer); multimap<std::string, std::string> meta; meta.insert(pair<string, string>("generator", "Plant Mock-Up Converter 0.1")); m_writers.back()->startX3DDocument(Immersive, VERSION_3_0, &meta, false); startNode(ID::Background); m_writers.back()->setSFColor(ID::skyColor, .9f, .9f, .9f); endNode(ID::Background); } startNode(ID::Transform); // Problems with encoding and unicity so changed name from DEF storage to metadata. See after translation. //m_writers.back()->setSFString(ID::DEF, x3dName); /*m_writers.back()->setSFVec3f(ID::translation, (translation[0] - m_translations.back()[0]), (translation[1] - m_translations.back()[1]), (translation[2] - m_translations.back()[2]));*/ m_translations.push_back(translation); // PDMS name as metadata. // writeMetaDataString("pdms", name); }
void X3DConverter::startModel(const string& projectName, const string& name) { startNode(ID::WorldInfo); MFString info; info.push_back("info"); m_writers.back()->setMFString(ID::info, info); startNode(ID::MetadataSet); if (!projectName.empty()) { writeMetaDataString("projectName", projectName, true); } if (!name.empty()) { writeMetaDataString("name", name, true); } endNode(ID::MetadataSet); // MetaDataSet endNode(ID::WorldInfo); // WorldInfo startNode(ID::Background); m_writers.back()->setSFColor(ID::skyColor, .9f, .9f, .9f); endNode(ID::Background); }
MFString operator+(const char *pString, const MFString &string) { if(string.IsEmpty()) return MFString(pString); if(!pString || *pString == 0) return string; return MFString::Static(pString) + string; }
void HKWidgetRendererLabel::Render(const HKWidget &widget, const MFMatrix &worldTransform) { HKWidgetRenderer::Render(widget, worldTransform); HKWidgetLabel &label = (HKWidgetLabel&)widget; MFString l = label.GetText(); if(!l.IsEmpty()) { MFFont *pFont = label.GetFont(); float height = label.GetTextHeight(); float shadowDepth = label.GetShadowDepth(); const MFVector &size = widget.GetSize(); const MFVector &colour = widget.GetColour(); const MFVector &textColour = label.GetTextColour(); MFFontJustify j = label.GetTextJustification(); const char *pString = l.CStr(); if(shadowDepth > 0.f) MFFont_DrawTextJustified(pFont, pString, MakeVector(shadowDepth, shadowDepth), size.x, size.y, j, height, MFVector::black, -1, worldTransform); MFFont_DrawTextJustified(pFont, pString, MFVector::zero, size.x, size.y, j, height, textColour * colour, -1, worldTransform); } }
void Scan(MFString path) { MFFindData fd; MFFind *pFind = MFFileSystem_FindFirst(MFStr("%s*", path.CStr()), &fd); if(pFind) { do { if(fd.attributes & MFFA_Directory) { Scan(MFString::Format("%s%s/", path.CStr(), fd.pFilename).CStr()); } else { MFString ext = MFString(fd.pFilename).GetExtension(); if(ext.Enumerate(ppFormats, sizeof(ppFormats) / sizeof(ppFormats[0])) > -1) models.push(MFString::Format("%s%s", path.CStr(), fd.pFilename)); } } while(MFFileSystem_FindNext(pFind, &fd)); MFFileSystem_FindClose(pFind); } }
MFTOCEntry* MFFileSystem_BuildToc(const char *pFindPattern, MFTOCEntry *pToc, MFTOCEntry *pParent, char* &pStringCache, bool recursive, bool flatten) { MFFindData findData; MFFind *hFind; hFind = MFFileSystem_FindFirst(MFString::Format("%s*", pFindPattern).CStr(), &findData); char *pCurrentDir = pStringCache; if(hFind) { MFString_Copy(pCurrentDir, findData.pSystemPath); pStringCache += MFString_Length(findData.pSystemPath) + 1; } while(hFind) { if(MFString_Compare(findData.pFilename, ".") && MFString_Compare(findData.pFilename, "..") && MFString_Compare(findData.pFilename, ".svn")) { if(findData.info.attributes & MFFA_Directory) { if(recursive) { MFString newPath = MFString::Format("%s%s/", pFindPattern, findData.pFilename); if(flatten) { pToc = MFFileSystem_BuildToc(newPath.CStr(), pToc, pParent, pStringCache, recursive, flatten); } else { size_t stringCacheSize = 0; pToc->numChildren = MFFileSystem_GetNumEntries(newPath.CStr(), recursive, flatten, &stringCacheSize); MFString_Copy(pStringCache, findData.pFilename); pToc->pName = pStringCache; pStringCache += MFString_Length(findData.pFilename)+1; pToc->info = findData.info; pToc->pFilesysData = pCurrentDir; pToc->pParent = pParent; if (pToc->numChildren) { size_t sizeOfToc = sizeof(MFTOCEntry)*pToc->numChildren; pToc->pChildren = (MFTOCEntry*)MFHeap_Alloc(sizeOfToc + stringCacheSize); char *pNewStringCache = (char*)pToc->pChildren + sizeOfToc; MFFileSystem_BuildToc(newPath.CStr(), pToc->pChildren, pToc, pNewStringCache, recursive, flatten); } else pToc->pChildren = NULL; ++pToc; } } } else { MFString_Copy(pStringCache, findData.pFilename); pToc->pName = pStringCache; pStringCache += MFString_Length(findData.pFilename)+1; pToc->pFilesysData = pCurrentDir; pToc->pParent = pParent; pToc->pChildren = NULL; pToc->numChildren = 0; pToc->info = findData.info; ++pToc; } } if(!MFFileSystem_FindNext(hFind, &findData)) { MFFileSystem_FindClose(hFind); hFind = NULL; } } return pToc; }
MFTOCEntry* MFFileSystem_BuildToc(const char *pFindPattern, MFTOCEntry *pToc, MFTOCEntry *pParent, char* &pStringCache, bool recursive, bool flatten) { MFFindData findData; MFFind *hFind; hFind = MFFileSystem_FindFirst(MFString::Format("%s*", pFindPattern).CStr(), &findData); char *pCurrentDir = pStringCache; if(hFind) { MFString_Copy(pCurrentDir, findData.pSystemPath); pStringCache += MFString_Length(findData.pSystemPath) + 1; } while(hFind) { if(MFString_Compare(findData.pFilename, ".") && MFString_Compare(findData.pFilename, "..") && MFString_Compare(findData.pFilename, ".svn")) { if(findData.attributes & MFFA_Directory) { if(recursive) { MFString newPath = MFString::Format("%s%s/", pFindPattern, findData.pFilename); if(flatten) { pToc = MFFileSystem_BuildToc(newPath.CStr(), pToc, pParent, pStringCache, recursive, flatten); } else { size_t stringCacheSize = 0; pToc->size = MFFileSystem_GetNumEntries(newPath.CStr(), recursive, flatten, &stringCacheSize); if(pToc->size) { MFString_Copy(pStringCache, findData.pFilename); pToc->pName = pStringCache; pStringCache += MFString_Length(findData.pFilename)+1; pToc->flags = MFTF_Directory; pToc->pFilesysData = pCurrentDir; pToc->pParent = pParent; pToc->size = 0; size_t sizeOfToc = sizeof(MFTOCEntry)*pToc->size; pToc->pChild = (MFTOCEntry*)MFHeap_Alloc(sizeof(MFTOCEntry)*sizeOfToc + stringCacheSize); char *pNewStringCache = ((char*)pToc->pChild)+sizeOfToc; MFFileSystem_BuildToc(newPath.CStr(), pToc->pChild, pToc, pNewStringCache, recursive, flatten); ++pToc; } } } } else { MFString_Copy(pStringCache, findData.pFilename); pToc->pName = pStringCache; pStringCache += MFString_Length(findData.pFilename)+1; pToc->pFilesysData = pCurrentDir; pToc->pParent = pParent; pToc->pChild = NULL; MFDebug_Assert(findData.fileSize < 0x100000000LL, "Files larger than 4gb not yet supported..."); pToc->size = (uint32)findData.fileSize; pToc->flags = 0; ++pToc; } } if(!MFFileSystem_FindNext(hFind, &findData)) { MFFileSystem_FindClose(hFind); hFind = NULL; } } return pToc; }
int MFString::Parse(const char *pFormat, ...) { if(!pData || !pFormat) return 0; va_list arglist; va_start(arglist, pFormat); const char *pS = pData->pMemory; int numArgs = 0; MFString format = GetNextBit(pFormat); int numChars = Match(pS, format.CStr()); while(*pFormat && numChars >= 0) { pS += numChars; ++pFormat; int length = -1; // gather format options... while(*pFormat) { int c = MFToLower(*pFormat++); if(c == 's') { MFString *pStr = va_arg(arglist, MFString*); ++numArgs; format = GetNextBit(pFormat); if(length >= 0) { MFString s(pS, (size_t)length); *pStr = s; numChars = Match(pS, format.CStr()); } else if(format.NumBytes() == 0) { *pStr = pS; } else { const char *pEnd = pS; while(*pEnd && (numChars = Match(pEnd, format.CStr())) < 0) ++pEnd; MFString s(pS, (size_t)(pEnd - pS)); *pStr = s; } pS += pStr->NumBytes(); break; } else if(c == 'd' || c == 'i') { int *pInt = va_arg(arglist, int*); ++numArgs; bool bNeg = *pS == '-'; if(*pS == '-' || *pS == '+') ++pS; *pInt = 0; while(MFIsNumeric(*pS) && ((uint32&)length)-- > 0) *pInt = *pInt*10 + *pS++ - '0'; if(bNeg) *pInt = -*pInt; format = GetNextBit(pFormat); numChars = Match(pS, format.CStr()); break; }
int F3DFile::ReadMD3(const char *pFilename) { pModel = this; char *pBuffer = NULL; // open .pk3 file unzFile zipFile = unzOpen(pFilename); // iterate files in zip int zipFileIndex = unzGoToFirstFile(zipFile); while(zipFileIndex != UNZ_END_OF_LIST_OF_FILE) { MFDebug_Assert(zipFileIndex == UNZ_OK, "Error in .zip file."); char fileNameBuf[256]; unz_file_info fileInfo; unzGetCurrentFileInfo(zipFile, &fileInfo, fileNameBuf, sizeof(fileNameBuf), NULL, 0, NULL, 0); MFString fileName = fileNameBuf; if(fileName.EndsWith(".md3")) { // read fle from zip pBuffer = (char*)malloc(fileInfo.uncompressed_size); unzOpenCurrentFile(zipFile); uint32 bytesRead = unzReadCurrentFile(zipFile, pBuffer, fileInfo.uncompressed_size); unzCloseCurrentFile(zipFile); MFDebug_Assert(bytesRead == fileInfo.uncompressed_size, "Incorrect number of bytes read.."); // get subobject and model name.. int slash = MFMax(fileName.FindCharReverse('/'), fileName.FindCharReverse('\\')); MFString subobjectName = fileName.SubStr(slash + 1); MFString modelName = fileName.SubStr(0, slash); slash = MFMax(modelName.FindCharReverse('/'), modelName.FindCharReverse('\\')); pModel->name = modelName.SubStr(slash + 1); // search for skin file MFString skinFilename = fileName; skinFilename.TruncateExtension(); skinFilename += "_"; skinFilename += pModel->name; skinFilename += ".skin"; // attempt to read skin.. char *pSkinFile = NULL; zipFileIndex = unzLocateFile(zipFile, skinFilename.CStr(), 0); if(zipFileIndex != UNZ_END_OF_LIST_OF_FILE) { // read skin file from zip unz_file_info skinInfo; char skinName[256]; unzGetCurrentFileInfo(zipFile, &skinInfo, skinName, 256, NULL, 0, NULL, 0); pSkinFile = (char*)MFHeap_TAlloc(skinInfo.uncompressed_size + 1); pSkinFile[skinInfo.uncompressed_size] = 0; unzOpenCurrentFile(zipFile); uint32 skinBytesRead = unzReadCurrentFile(zipFile, pSkinFile, skinInfo.uncompressed_size); unzCloseCurrentFile(zipFile); MFDebug_Assert(skinBytesRead == skinInfo.uncompressed_size, "Incorrect number of bytes read.."); } zipFileIndex = unzLocateFile(zipFile, fileName.CStr(), 0); // parse MD3 ParseMD3File(pBuffer, fileInfo.uncompressed_size, subobjectName.CStr(), pSkinFile); // free file MFHeap_Free(pBuffer); pBuffer = NULL; } /* else if(!MFString_CaseCmp(".skin", &fileName[Max(filenameLen - 5, 0)])) { int a, b; char skinName[256]; // get subobject and model name for(a = filenameLen - 5; a >= 0; a--) { if(fileName[a] == '/' || fileName[a] == '\\') break; } char *pSkinName = &fileName[a+1]; for(b = a-1; b >= 0; b--) { if(fileName[b] == '/' || fileName[b] == '\\') break; } MFString_Copy(skinName, &fileName[b+1]); skinName[a-b-1] = 0; pSkinName = strchr(pSkinName, '_'); DBGASSERT(pSkinName, "Incorrectly named .skin file in .pk3"); ++pSkinName; // check that this is the default skin for this model if(!MFString_CaseCmpN(pSkinName, skinName, MFString_Length(skinName))) { // read material file from zip pBuffer = (char*)malloc(fileInfo.uncompressed_size); unzOpenCurrentFile(zipFile); uint32 bytesRead = unzReadCurrentFile(zipFile, pBuffer, fileInfo.uncompressed_size); unzCloseCurrentFile(zipFile); // parse .skin file free(pBuffer); pBuffer = NULL; } else { // we have an alternate skin.. do nothing.. } } */ zipFileIndex = unzGoToNextFile(zipFile); } // close .pk3 file unzClose(zipFile); return 0; }
MF_API MFModel* MFModel_Create(const char *pFilename) { const char* pOriginalFilename = pFilename; // see if it's already loaded MFModelPool::Iterator it = gModelBank.Get(pOriginalFilename); MFModelTemplate *pTemplate = it ? *it : NULL; if(!pTemplate) { char *pTemplateData = NULL; MFFile *hFile = MFFileSystem_Open(MFStr("%s.mdl", pFilename), MFOF_Read|MFOF_Binary); if(hFile) { int64 size = MFFile_GetSize(hFile); if(size > 0) { // allocate memory and load file pTemplateData = (char*)MFHeap_Alloc((size_t)size + MFString_Length(pFilename) + 1); MFFile_Read(hFile, pTemplateData, (size_t)size); MFFile_Close(hFile); MFString_Copy(&pTemplateData[size], pFilename); pFilename = &pTemplateData[size]; } } else { #if defined(ALLOW_LOAD_FROM_SOURCE_DATA) // try to load from source data const char * const pExt[] = { ".f3d", ".dae", ".x", ".ase", ".obj", ".md2", ".md3", ".me2", NULL }; const char * const *ppExt = pExt; MFIntModel *pIM = NULL; while(!pIM && *ppExt) { MFString tempFilename = MFString::Format("%s%s", pFilename, *ppExt); pIM = MFIntModel_CreateFromFile(tempFilename.CStr()); if(pIM) { pFilename = MFString_Copy((char*)MFHeap_Alloc(tempFilename.NumBytes()+1), tempFilename.CStr()); break; } ++ppExt; } if(pIM) { MFIntModel_Optimise(pIM); size_t size; MFIntModel_CreateRuntimeData(pIM, (void**)&pTemplateData, &size, MFSystem_GetCurrentPlatform()); MFFile *pFile = MFFileSystem_Open(MFStr("cache:%s.mdl", pOriginalFilename), MFOF_Write | MFOF_Binary); if(pFile) { MFFile_Write(pFile, pTemplateData, size, false); MFFile_Close(pFile); } MFIntModel_Destroy(pIM); } #endif } if(!pTemplateData) return NULL; // check ID string MFDebug_Assert(*(uint32*)pTemplateData == MFMAKEFOURCC('M', 'D', 'L', '2'), "Incorrect MFModel version."); // store filename for later reference pTemplate = (MFModelTemplate*)pTemplateData; pTemplate->pFilename = pFilename; gModelBank.Add(pOriginalFilename, pTemplate); MFModel_FixUp(pTemplate, true); MFModelDataChunk *pChunk = MFModel_GetDataChunk(pTemplate, MFChunkType_SubObjects); if(pChunk) { MFModelSubObject *pSubobjects = (MFModelSubObject*)pChunk->pData; for(int a=0; a<pChunk->count; a++) { // pSubobjects[a].pMaterial = MFMaterial_Create((char*)pSubobjects[a].pMaterial); for(int b=0; b<pSubobjects[a].numMeshChunks; b++) { MFModel_CreateMeshChunk(MFModel_GetMeshChunkInternal(pTemplate, a, b)); } } } } MFModel *pModel; pModel = (MFModel*)MFHeap_Alloc(sizeof(MFModel)); pModel->worldMatrix = MFMatrix::identity; pModel->modelColour = MFVector::one; pModel->pTemplate = pTemplate; pModel->pAnimation = NULL; ++pTemplate->refCount; return pModel; }