Fix issue in Rocket.lua script.
[Cafu-Engine.git] / Libs / MaterialSystem / MaterialManagerImpl.cpp
blobfb272d23ce32afe1067d1ed5a902663fb4798cfe
1 /*
2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
5 */
7 /***************************************/
8 /*** Material Manager Implementation ***/
9 /***************************************/
11 #include <math.h>
12 #include <stdio.h>
14 #ifdef _WIN32
15 #include <direct.h>
16 #if defined(_MSC_VER)
17 #define WIN32_LEAN_AND_MEAN
18 #include <windows.h>
19 #endif
20 #else
21 #include <cstring>
22 #include <dirent.h>
23 #endif
25 #include "MaterialManagerImpl.hpp"
26 #include "Expression.hpp"
27 #include "Material.hpp"
28 #include "ConsoleCommands/Console.hpp"
29 #include "String.hpp"
30 #include "TextParser/TextParser.hpp"
33 static TableT* ParseTable(TextParserT& TP)
35 // The "table" keyword has already been parsed by the caller.
36 TableT Table;
38 Table.Name=TP.GetNextToken();
40 Table.ColumnSnap =false; // Per default, interpolate across columns.
41 Table.ColumnClamp=false; // Per default, wrap across columns.
43 // Start table parsing.
44 while (true)
46 std::string Token=TP.GetNextToken();
48 if (Token=="{" ) break; // Start of table definition.
49 else if (Token=="snap" ) Table.ColumnSnap =true; // Snap to column values (don't interpolate).
50 else if (Token=="clamp") Table.ColumnClamp=true; // Clamp columns (don't wrap).
51 else return NULL; // Invalid token.
54 // Parse the table definition.
55 bool NextRowSnap =false;
56 bool NextRowClamp=false;
58 while (true)
60 std::string Token=TP.GetNextToken();
62 if (Token=="}" ) break; // End of table definition.
63 else if (Token=="snap" ) NextRowSnap =true; // Snap new row to row values (don't interpolate).
64 else if (Token=="clamp") NextRowClamp=true; // Clamp next row (don't wrap).
65 else if (Token=="{") // Start of row definition.
67 // Parse row.
68 Table.Rows.PushBackEmpty();
69 Table.Rows[Table.Rows.Size()-1].RowSnap =NextRowSnap;
70 Table.Rows[Table.Rows.Size()-1].RowClamp=NextRowClamp;
72 Token=TP.GetNextToken();
74 if (Token!="}") // If Token=="}", the row is empty.
76 while (true)
78 // The token should be a number, followed either by a comma or the closing bracket.
79 Table.Rows[Table.Rows.Size()-1].Data.PushBack(float(atof(Token.c_str())));
81 Token=TP.GetNextToken();
83 if (Token=="}") break; // End of row definition.
84 if (Token==",") { Token=TP.GetNextToken(); continue; } // Continue with next number.
85 return NULL; // Not a '}' and not a ',' after a number - invalid token.
89 // Reset for next row (if any).
90 NextRowSnap =false;
91 NextRowClamp=false;
93 else return NULL; // Invalid token.
96 return new TableT(Table);
100 MaterialManagerImplT::MaterialManagerImplT() : MaterialManagerI()
102 const double Pi=3.14159265358979323846;
104 // Add sinTable as a global default table.
105 Tables.PushBack(new TableT);
106 Tables[0]->Name="sinTable";
107 Tables[0]->Rows.PushBackEmpty();
108 Tables[0]->Rows[0].RowSnap =false;
109 Tables[0]->Rows[0].RowClamp=false;
110 for (unsigned long i=0; i<256; i++) Tables[0]->Rows[0].Data.PushBack(float(sin(double(i)/256.0*2.0*Pi)));
112 // Add cosTable as a global default table.
113 Tables.PushBack(new TableT);
114 Tables[1]->Name="cosTable";
115 Tables[1]->Rows.PushBackEmpty();
116 Tables[1]->Rows[0].RowSnap =false;
117 Tables[1]->Rows[0].RowClamp=false;
118 for (unsigned long i=0; i<256; i++) Tables[1]->Rows[0].Data.PushBack(float(cos(double(i)/256.0*2.0*Pi)));
120 // Add sinTable01 as a global default table. sinTable01[i] == sinTable[i]/2.0+0.5
121 Tables.PushBack(new TableT);
122 Tables[2]->Name="sinTable01";
123 Tables[2]->Rows.PushBackEmpty();
124 Tables[2]->Rows[0].RowSnap =false;
125 Tables[2]->Rows[0].RowClamp=false;
126 for (unsigned long i=0; i<256; i++) Tables[2]->Rows[0].Data.PushBack(Tables[0]->Rows[0].Data[i]/2.0f+0.5f);
128 // Add cosTable01 as a global default table. cosTable[i] == cosTable[i]/2.0+0.5
129 Tables.PushBack(new TableT);
130 Tables[3]->Name="cosTable01";
131 Tables[3]->Rows.PushBackEmpty();
132 Tables[3]->Rows[0].RowSnap =false;
133 Tables[3]->Rows[0].RowClamp=false;
134 for (unsigned long i=0; i<256; i++) Tables[3]->Rows[0].Data.PushBack(Tables[1]->Rows[0].Data[i]/2.0f+0.5f);
136 // WARNING: Adding more tables here also requires changes in MaterialManagerImplT::ClearAllMaterials().
140 MaterialManagerImplT::~MaterialManagerImplT()
142 for (unsigned long TableNr=0; TableNr<Tables.Size(); TableNr++)
143 delete Tables[TableNr];
144 Tables.Clear();
146 for (std::map<std::string, MaterialT*>::const_iterator Mat=Materials.begin(); Mat!=Materials.end(); Mat++)
147 delete Mat->second;
148 Materials.clear();
152 MaterialT* MaterialManagerImplT::RegisterMaterial(const MaterialT& Mat)
154 MaterialT*& MatPtr=Materials[Mat.Name];
156 // Creating a new copy of Mat here makes it clear to the user who has ownership of Mat (the user)
157 // and who has ownership of the registered material (we, the material manager).
158 // It also ensures that there is never a danger to register the same material instance accidently twice,
159 // as would be possible if we registered a user-created material instance whose name got possibly mangled
160 // to resolve name collisions.
161 if (!MatPtr) MatPtr=new MaterialT(Mat);
163 return MatPtr;
167 ArrayT<MaterialT*> MaterialManagerImplT::RegisterMaterialScript(const std::string& FileName, const std::string& BaseDir)
169 ArrayT<MaterialT*> NewMaterials;
171 for (unsigned long Nr=0; Nr<MaterialScriptFileNames.Size(); Nr++)
172 if (MaterialScriptFileNames[Nr]==FileName) return NewMaterials;
174 MaterialScriptFileNames.PushBack(FileName);
176 // Materials in a script do only see the locally defined tables.
177 ArrayT<TableT*> ThisScriptsTables;
178 ThisScriptsTables.PushBack(Tables[0]); // The sinTable is global.
179 ThisScriptsTables.PushBack(Tables[1]); // The cosTable is global.
180 ThisScriptsTables.PushBack(Tables[2]); // The sinTable01 is global.
181 ThisScriptsTables.PushBack(Tables[3]); // The cosTable01 is global.
183 // Get the materials from the script.
184 TextParserT TextParser(FileName.c_str(), "({[]}),");
188 while (!TextParser.IsAtEOF())
190 const std::string Token=TextParser.GetNextToken();
192 if (Token=="table")
194 TableT* T=ParseTable(TextParser);
196 // If the table could not be parsed (e.g. due to a syntax error or unknown token),
197 // abort the parsing of the entire file - the file might be something else than a material script.
198 // Even if it was, we cannot easily continue anyway.
199 if (!T)
201 Console->Print("Error parsing "+FileName+" near "+Token+cf::va(" at input byte %lu.\n", TextParser.GetReadPosByte()));
202 break;
205 // We do not check for duplicate tables.
206 Tables.PushBack(T);
207 ThisScriptsTables.PushBack(T);
209 else if (Token=="dofile")
211 TextParser.AssertAndSkipToken("(");
212 std::string Include=TextParser.GetNextToken();
213 TextParser.AssertAndSkipToken(")");
215 NewMaterials.PushBack(RegisterMaterialScript(BaseDir+Include, BaseDir+cf::String::GetPath(Include)+"/"));
217 else
219 // If the material cannot be parsed (e.g. due to a syntax error or unknown token),
220 // the parsing of the entire file is aborted - the file might be something else than a material script.
221 // Even if it was, we cannot easily continue anyway.
223 MaterialT* NewMat=new MaterialT(Token, BaseDir, TextParser, ThisScriptsTables);
224 MaterialT*& Mat =Materials[NewMat->Name];
226 if (Mat==NULL)
228 Mat=NewMat;
229 NewMaterials.PushBack(NewMat);
231 else
233 Console->Print("File " + FileName + ", material \"" + NewMat->Name + "\": duplicate definition (ignored).\n");
234 delete NewMat;
239 catch (const TextParserT::ParseError&)
241 Console->Print("Error parsing "+FileName+cf::va(" at input byte %lu.\n", TextParser.GetReadPosByte()));
244 return NewMaterials;
248 ArrayT<MaterialT*> MaterialManagerImplT::RegisterMaterialScriptsInDir(const std::string& DirName, const std::string& BaseDir, const bool Recurse)
250 ArrayT<MaterialT*> NewMaterials;
252 if (DirName=="") return NewMaterials;
254 #ifdef _MSC_VER
255 WIN32_FIND_DATA FindFileData;
257 HANDLE hFind=FindFirstFile((DirName+"\\*").c_str(), &FindFileData);
259 // MessageBox(NULL, hFind==INVALID_HANDLE_VALUE ? "INV_VALUE!!" : FindFileData.cFileName, "Starting parsing.", MB_ICONINFORMATION);
260 if (hFind==INVALID_HANDLE_VALUE) return NewMaterials;
262 ArrayT<std::string> DirEntNames;
266 // MessageBox(NULL, FindFileData.cFileName, "Material Script Found", MB_ICONINFORMATION);
267 if (!_stricmp(FindFileData.cFileName, "." )) continue;
268 if (!_stricmp(FindFileData.cFileName, ".." )) continue;
269 if (!_stricmp(FindFileData.cFileName, "cvs")) continue;
271 DirEntNames.PushBack(DirName+"/"+FindFileData.cFileName);
272 } while (FindNextFile(hFind, &FindFileData)!=0);
274 if (GetLastError()==ERROR_NO_MORE_FILES) FindClose(hFind);
275 #else
276 DIR* Dir=opendir(DirName.c_str());
278 if (!Dir) return NewMaterials;
280 ArrayT<std::string> DirEntNames;
282 for (dirent* DirEnt=readdir(Dir); DirEnt!=NULL; DirEnt=readdir(Dir))
284 if (!strcasecmp(DirEnt->d_name, "." )) continue;
285 if (!strcasecmp(DirEnt->d_name, ".." )) continue;
286 if (!strcasecmp(DirEnt->d_name, "cvs")) continue;
288 // For portability, only the 'd_name' member of a 'dirent' may be accessed.
289 DirEntNames.PushBack(DirName+"/"+DirEnt->d_name);
292 closedir(Dir);
293 #endif
296 for (unsigned long DENr=0; DENr<DirEntNames.Size(); DENr++)
298 #ifdef _WIN32
299 bool IsDirectory=true;
301 FILE* TempFile=fopen(DirEntNames[DENr].c_str(), "r");
302 if (TempFile!=NULL)
304 // This was probably a file (instead of a directory).
305 fclose(TempFile);
306 IsDirectory=false;
308 #else
309 bool IsDirectory=false;
311 // Sigh. And this doesn't work under Win32... (opendir does not return NULL for files!?!)
312 DIR* TempDir=opendir(DirEntNames[DENr].c_str());
313 if (TempDir!=NULL)
315 // This was a directory.
316 closedir(TempDir);
317 IsDirectory=true;
319 #endif
321 if (IsDirectory)
323 if (Recurse)
324 NewMaterials.PushBack(RegisterMaterialScriptsInDir(DirEntNames[DENr], BaseDir));
326 else
328 if (DirEntNames[DENr].length()>5)
329 if (std::string(DirEntNames[DENr].c_str()+DirEntNames[DENr].length()-5)==".cmat")
330 NewMaterials.PushBack(RegisterMaterialScript(DirEntNames[DENr], BaseDir));
334 return NewMaterials;
338 bool MaterialManagerImplT::HasMaterial(const std::string& MaterialName) const
340 std::map<std::string, MaterialT*>::const_iterator It=Materials.find(MaterialName);
342 return It!=Materials.end();
346 MaterialT* MaterialManagerImplT::GetMaterial(const std::string& MaterialName) const
348 // Note that we are *not* just writing return Materials[MaterialName] here, because that
349 // would implicitly create a NULL entry for every MaterialName that does not actually exist.
350 std::map<std::string, MaterialT*>::const_iterator It=Materials.find(MaterialName);
352 if (It!=Materials.end()) return It->second;
354 Console->Print(cf::va("%s (%u): ", __FILE__, __LINE__)+"GetMaterial(\""+MaterialName+"\") returns NULL.\n");
355 return NULL;
359 // void MaterialManagerImplT::ClearAllMaterials()
360 // {
361 // MaterialScriptFileNames.Clear();
363 // // Can well delete Tables (except the first four stock tables!), all expressions that refer to one have made their own copies.
364 // for (unsigned long TableNr=4; TableNr<Tables.Size(); TableNr++)
365 // delete Tables[TableNr];
366 // while (Tables.Size()>4) Tables.DeleteBack();
368 // for (unsigned long MaterialNr=0; MaterialNr<Materials.Size(); MaterialNr++)
369 // delete Materials[MaterialNr];
370 // Materials.Clear();
371 // }