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.
7 /***************************************/
8 /*** Material Manager Implementation ***/
9 /***************************************/
17 #define WIN32_LEAN_AND_MEAN
25 #include "MaterialManagerImpl.hpp"
26 #include "Expression.hpp"
27 #include "Material.hpp"
28 #include "ConsoleCommands/Console.hpp"
30 #include "TextParser/TextParser.hpp"
33 static TableT
* ParseTable(TextParserT
& TP
)
35 // The "table" keyword has already been parsed by the caller.
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.
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;
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.
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.
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).
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
];
146 for (std::map
<std::string
, MaterialT
*>::const_iterator Mat
=Materials
.begin(); Mat
!=Materials
.end(); Mat
++)
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
);
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();
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.
201 Console
->Print("Error parsing "+FileName
+" near "+Token
+cf::va(" at input byte %lu.\n", TextParser
.GetReadPosByte()));
205 // We do not check for duplicate tables.
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
)+"/"));
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
];
229 NewMaterials
.PushBack(NewMat
);
233 Console
->Print("File " + FileName
+ ", material \"" + NewMat
->Name
+ "\": duplicate definition (ignored).\n");
239 catch (const TextParserT::ParseError
&)
241 Console
->Print("Error parsing "+FileName
+cf::va(" at input byte %lu.\n", TextParser
.GetReadPosByte()));
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
;
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
);
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
);
296 for (unsigned long DENr
=0; DENr
<DirEntNames
.Size(); DENr
++)
299 bool IsDirectory
=true;
301 FILE* TempFile
=fopen(DirEntNames
[DENr
].c_str(), "r");
304 // This was probably a file (instead of a directory).
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());
315 // This was a directory.
324 NewMaterials
.PushBack(RegisterMaterialScriptsInDir(DirEntNames
[DENr
], BaseDir
));
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
));
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");
359 // void MaterialManagerImplT::ClearAllMaterials()
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();