1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
2 // This file is part of the "Irrlicht Engine".
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
4
5 #include "IrrCompileConfig.h"
6 #ifdef _IRR_COMPILE_WITH_BSP_LOADER_
7
8 #include "CQ3LevelMesh.h"
9 #include "ISceneManager.h"
10 #include "os.h"
11 #include "SMeshBufferLightMap.h"
12 #include "irrString.h"
13 #include "ILightSceneNode.h"
14 #include "IQ3Shader.h"
15 #include "IFileList.h"
16
17 //#define TJUNCTION_SOLVER_ROUND
18 //#define TJUNCTION_SOLVER_0125
19
20 namespace irr
21 {
22 namespace scene
23 {
24
25 using namespace quake3;
26
27 //! constructor
CQ3LevelMesh(io::IFileSystem * fs,scene::ISceneManager * smgr,const Q3LevelLoadParameter & loadParam)28 CQ3LevelMesh::CQ3LevelMesh(io::IFileSystem* fs, scene::ISceneManager* smgr,
29 const Q3LevelLoadParameter &loadParam)
30 : LoadParam(loadParam), Textures(0), NumTextures(0), LightMaps(0), NumLightMaps(0),
31 Vertices(0), NumVertices(0), Faces(0), NumFaces(0), Models(0), NumModels(0),
32 Planes(0), NumPlanes(0), Nodes(0), NumNodes(0), Leafs(0), NumLeafs(0),
33 LeafFaces(0), NumLeafFaces(0), MeshVerts(0), NumMeshVerts(0),
34 Brushes(0), NumBrushes(0), BrushEntities(0), FileSystem(fs),
35 SceneManager(smgr), FramesPerSecond(25.f)
36 {
37 #ifdef _DEBUG
38 IReferenceCounted::setDebugName("CQ3LevelMesh");
39 #endif
40
41 for ( s32 i = 0; i!= E_Q3_MESH_SIZE; ++i )
42 {
43 Mesh[i] = 0;
44 }
45
46 Driver = smgr ? smgr->getVideoDriver() : 0;
47 if (Driver)
48 Driver->grab();
49
50 if (FileSystem)
51 FileSystem->grab();
52
53 // load default shaders
54 InitShader();
55 }
56
57
58 //! destructor
~CQ3LevelMesh()59 CQ3LevelMesh::~CQ3LevelMesh()
60 {
61 cleanLoader ();
62
63 if (Driver)
64 Driver->drop();
65
66 if (FileSystem)
67 FileSystem->drop();
68
69 s32 i;
70
71 for ( i = 0; i!= E_Q3_MESH_SIZE; ++i )
72 {
73 if ( Mesh[i] )
74 {
75 Mesh[i]->drop();
76 Mesh[i] = 0;
77 }
78 }
79
80 for ( i = 1; i < NumModels; i++ )
81 {
82 BrushEntities[i]->drop();
83 }
84 delete [] BrushEntities; BrushEntities = 0;
85
86 ReleaseShader();
87 ReleaseEntity();
88 }
89
90
91 //! loads a level from a .bsp-File. Also tries to load all needed textures. Returns true if successful.
loadFile(io::IReadFile * file)92 bool CQ3LevelMesh::loadFile(io::IReadFile* file)
93 {
94 if (!file)
95 return false;
96
97 LevelName = file->getFileName();
98
99 file->read(&header, sizeof(tBSPHeader));
100
101 #ifdef __BIG_ENDIAN__
102 header.strID = os::Byteswap::byteswap(header.strID);
103 header.version = os::Byteswap::byteswap(header.version);
104 #endif
105
106 if ( (header.strID != 0x50534249 || // IBSP
107 ( header.version != 0x2e // quake3
108 && header.version != 0x2f // rtcw
109 )
110 )
111 &&
112 ( header.strID != 0x50534252 || header.version != 1 ) // RBSP, starwars jedi, sof
113 )
114 {
115 os::Printer::log("Could not load .bsp file, unknown header.", file->getFileName(), ELL_ERROR);
116 return false;
117 }
118
119 #if 0
120 if ( header.strID == 0x50534252 ) // RBSP Raven
121 {
122 LoadParam.swapHeader = 1;
123 }
124 #endif
125
126 // now read lumps
127 file->read(&Lumps[0], sizeof(tBSPLump)*kMaxLumps);
128
129 s32 i;
130 if ( LoadParam.swapHeader )
131 {
132 for ( i=0; i< kMaxLumps;++i)
133 {
134 Lumps[i].offset = os::Byteswap::byteswap(Lumps[i].offset);
135 Lumps[i].length = os::Byteswap::byteswap(Lumps[i].length);
136 }
137 }
138
139 ReleaseEntity();
140
141 // load everything
142 loadEntities(&Lumps[kEntities], file); // load the entities
143 loadTextures(&Lumps[kShaders], file); // Load the textures
144 loadLightmaps(&Lumps[kLightmaps], file); // Load the lightmaps
145 loadVerts(&Lumps[kVertices], file); // Load the vertices
146 loadFaces(&Lumps[kFaces], file); // Load the faces
147 loadPlanes(&Lumps[kPlanes], file); // Load the Planes of the BSP
148 loadNodes(&Lumps[kNodes], file); // load the Nodes of the BSP
149 loadLeafs(&Lumps[kLeafs], file); // load the Leafs of the BSP
150 loadLeafFaces(&Lumps[kLeafFaces], file); // load the Faces of the Leafs of the BSP
151 loadVisData(&Lumps[kVisData], file); // load the visibility data of the clusters
152 loadModels(&Lumps[kModels], file); // load the models
153 loadMeshVerts(&Lumps[kMeshVerts], file); // load the mesh vertices
154 loadBrushes(&Lumps[kBrushes], file); // load the brushes of the BSP
155 loadBrushSides(&Lumps[kBrushSides], file); // load the brushsides of the BSP
156 loadLeafBrushes(&Lumps[kLeafBrushes], file); // load the brushes of the leaf
157 loadFogs(&Lumps[kFogs], file ); // load the fogs
158
159 loadTextures();
160 constructMesh();
161 solveTJunction();
162
163 cleanMeshes();
164 calcBoundingBoxes();
165 cleanLoader();
166
167 return true;
168 }
169
170 /*!
171 */
cleanLoader()172 void CQ3LevelMesh::cleanLoader ()
173 {
174 delete [] Textures; Textures = 0;
175 delete [] LightMaps; LightMaps = 0;
176 delete [] Vertices; Vertices = 0;
177 delete [] Faces; Faces = 0;
178 delete [] Models; Models = 0;
179 delete [] Planes; Planes = 0;
180 delete [] Nodes; Nodes = 0;
181 delete [] Leafs; Leafs = 0;
182 delete [] LeafFaces; LeafFaces = 0;
183 delete [] MeshVerts; MeshVerts = 0;
184 delete [] Brushes; Brushes = 0;
185
186 Lightmap.clear();
187 Tex.clear();
188 }
189
190 //! returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh.
getFrameCount() const191 u32 CQ3LevelMesh::getFrameCount() const
192 {
193 return 1;
194 }
195
196
197 //! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level.
getMesh(s32 frameInMs,s32 detailLevel,s32 startFrameLoop,s32 endFrameLoop)198 IMesh* CQ3LevelMesh::getMesh(s32 frameInMs, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
199 {
200 return Mesh[frameInMs];
201 }
202
203
loadTextures(tBSPLump * l,io::IReadFile * file)204 void CQ3LevelMesh::loadTextures(tBSPLump* l, io::IReadFile* file)
205 {
206 NumTextures = l->length / sizeof(tBSPTexture);
207 if ( !NumTextures )
208 return;
209 Textures = new tBSPTexture[NumTextures];
210
211 file->seek(l->offset);
212 file->read(Textures, l->length);
213
214 if ( LoadParam.swapHeader )
215 {
216 for (s32 i=0;i<NumTextures;++i)
217 {
218 Textures[i].flags = os::Byteswap::byteswap(Textures[i].flags);
219 Textures[i].contents = os::Byteswap::byteswap(Textures[i].contents);
220 //os::Printer::log("Loaded texture", Textures[i].strName, ELL_INFORMATION);
221 }
222 }
223 }
224
225
loadLightmaps(tBSPLump * l,io::IReadFile * file)226 void CQ3LevelMesh::loadLightmaps(tBSPLump* l, io::IReadFile* file)
227 {
228 NumLightMaps = l->length / sizeof(tBSPLightmap);
229 if ( !NumLightMaps )
230 return;
231 LightMaps = new tBSPLightmap[NumLightMaps];
232
233 file->seek(l->offset);
234 file->read(LightMaps, l->length);
235 }
236
237 /*!
238 */
loadVerts(tBSPLump * l,io::IReadFile * file)239 void CQ3LevelMesh::loadVerts(tBSPLump* l, io::IReadFile* file)
240 {
241 NumVertices = l->length / sizeof(tBSPVertex);
242 if ( !NumVertices )
243 return;
244 Vertices = new tBSPVertex[NumVertices];
245
246 file->seek(l->offset);
247 file->read(Vertices, l->length);
248
249 if ( LoadParam.swapHeader )
250 for (s32 i=0;i<NumVertices;i++)
251 {
252 Vertices[i].vPosition[0] = os::Byteswap::byteswap(Vertices[i].vPosition[0]);
253 Vertices[i].vPosition[1] = os::Byteswap::byteswap(Vertices[i].vPosition[1]);
254 Vertices[i].vPosition[2] = os::Byteswap::byteswap(Vertices[i].vPosition[2]);
255 Vertices[i].vTextureCoord[0] = os::Byteswap::byteswap(Vertices[i].vTextureCoord[0]);
256 Vertices[i].vTextureCoord[1] = os::Byteswap::byteswap(Vertices[i].vTextureCoord[1]);
257 Vertices[i].vLightmapCoord[0] = os::Byteswap::byteswap(Vertices[i].vLightmapCoord[0]);
258 Vertices[i].vLightmapCoord[1] = os::Byteswap::byteswap(Vertices[i].vLightmapCoord[1]);
259 Vertices[i].vNormal[0] = os::Byteswap::byteswap(Vertices[i].vNormal[0]);
260 Vertices[i].vNormal[1] = os::Byteswap::byteswap(Vertices[i].vNormal[1]);
261 Vertices[i].vNormal[2] = os::Byteswap::byteswap(Vertices[i].vNormal[2]);
262 }
263 }
264
265
266 /*!
267 */
loadFaces(tBSPLump * l,io::IReadFile * file)268 void CQ3LevelMesh::loadFaces(tBSPLump* l, io::IReadFile* file)
269 {
270 NumFaces = l->length / sizeof(tBSPFace);
271 if (!NumFaces)
272 return;
273 Faces = new tBSPFace[NumFaces];
274
275 file->seek(l->offset);
276 file->read(Faces, l->length);
277
278 if ( LoadParam.swapHeader )
279 {
280 for ( s32 i=0;i<NumFaces;i++)
281 {
282 Faces[i].textureID = os::Byteswap::byteswap(Faces[i].textureID);
283 Faces[i].fogNum = os::Byteswap::byteswap(Faces[i].fogNum);
284 Faces[i].type = os::Byteswap::byteswap(Faces[i].type);
285 Faces[i].vertexIndex = os::Byteswap::byteswap(Faces[i].vertexIndex);
286 Faces[i].numOfVerts = os::Byteswap::byteswap(Faces[i].numOfVerts);
287 Faces[i].meshVertIndex = os::Byteswap::byteswap(Faces[i].meshVertIndex);
288 Faces[i].numMeshVerts = os::Byteswap::byteswap(Faces[i].numMeshVerts);
289 Faces[i].lightmapID = os::Byteswap::byteswap(Faces[i].lightmapID);
290 Faces[i].lMapCorner[0] = os::Byteswap::byteswap(Faces[i].lMapCorner[0]);
291 Faces[i].lMapCorner[1] = os::Byteswap::byteswap(Faces[i].lMapCorner[1]);
292 Faces[i].lMapSize[0] = os::Byteswap::byteswap(Faces[i].lMapSize[0]);
293 Faces[i].lMapSize[1] = os::Byteswap::byteswap(Faces[i].lMapSize[1]);
294 Faces[i].lMapPos[0] = os::Byteswap::byteswap(Faces[i].lMapPos[0]);
295 Faces[i].lMapPos[1] = os::Byteswap::byteswap(Faces[i].lMapPos[1]);
296 Faces[i].lMapPos[2] = os::Byteswap::byteswap(Faces[i].lMapPos[2]);
297 Faces[i].lMapBitsets[0][0] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][0]);
298 Faces[i].lMapBitsets[0][1] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][1]);
299 Faces[i].lMapBitsets[0][2] = os::Byteswap::byteswap(Faces[i].lMapBitsets[0][2]);
300 Faces[i].lMapBitsets[1][0] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][0]);
301 Faces[i].lMapBitsets[1][1] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][1]);
302 Faces[i].lMapBitsets[1][2] = os::Byteswap::byteswap(Faces[i].lMapBitsets[1][2]);
303 Faces[i].vNormal[0] = os::Byteswap::byteswap(Faces[i].vNormal[0]);
304 Faces[i].vNormal[1] = os::Byteswap::byteswap(Faces[i].vNormal[1]);
305 Faces[i].vNormal[2] = os::Byteswap::byteswap(Faces[i].vNormal[2]);
306 Faces[i].size[0] = os::Byteswap::byteswap(Faces[i].size[0]);
307 Faces[i].size[1] = os::Byteswap::byteswap(Faces[i].size[1]);
308 }
309 }
310 }
311
312
313 /*!
314 */
loadPlanes(tBSPLump * l,io::IReadFile * file)315 void CQ3LevelMesh::loadPlanes(tBSPLump* l, io::IReadFile* file)
316 {
317 // ignore
318 }
319
320
321 /*!
322 */
loadNodes(tBSPLump * l,io::IReadFile * file)323 void CQ3LevelMesh::loadNodes(tBSPLump* l, io::IReadFile* file)
324 {
325 // ignore
326 }
327
328
329 /*!
330 */
loadLeafs(tBSPLump * l,io::IReadFile * file)331 void CQ3LevelMesh::loadLeafs(tBSPLump* l, io::IReadFile* file)
332 {
333 // ignore
334 }
335
336
337 /*!
338 */
loadLeafFaces(tBSPLump * l,io::IReadFile * file)339 void CQ3LevelMesh::loadLeafFaces(tBSPLump* l, io::IReadFile* file)
340 {
341 // ignore
342 }
343
344
345 /*!
346 */
loadVisData(tBSPLump * l,io::IReadFile * file)347 void CQ3LevelMesh::loadVisData(tBSPLump* l, io::IReadFile* file)
348 {
349 // ignore
350 }
351
352
353 /*!
354 */
loadEntities(tBSPLump * l,io::IReadFile * file)355 void CQ3LevelMesh::loadEntities(tBSPLump* l, io::IReadFile* file)
356 {
357 core::array<u8> entity;
358 entity.set_used( l->length + 2 );
359 entity[l->length + 1 ] = 0;
360
361 file->seek(l->offset);
362 file->read( entity.pointer(), l->length);
363
364 parser_parse( entity.pointer(), l->length, &CQ3LevelMesh::scriptcallback_entity );
365 }
366
367
368 /*!
369 load fog brushes
370 */
loadFogs(tBSPLump * l,io::IReadFile * file)371 void CQ3LevelMesh::loadFogs(tBSPLump* l, io::IReadFile* file)
372 {
373 u32 files = l->length / sizeof(tBSPFog);
374
375 file->seek( l->offset );
376 tBSPFog fog;
377 const IShader *shader;
378 STexShader t;
379 for ( u32 i = 0; i!= files; ++i )
380 {
381 file->read( &fog, sizeof( fog ) );
382
383 shader = getShader( fog.shader );
384 t.Texture = 0;
385 t.ShaderID = shader ? shader->ID : -1;
386
387 FogMap.push_back ( t );
388 }
389 }
390
391
392 /*!
393 load models named in bsp
394 */
loadModels(tBSPLump * l,io::IReadFile * file)395 void CQ3LevelMesh::loadModels(tBSPLump* l, io::IReadFile* file)
396 {
397 NumModels = l->length / sizeof(tBSPModel);
398 Models = new tBSPModel[NumModels];
399
400 file->seek( l->offset );
401 file->read(Models, l->length);
402
403 if ( LoadParam.swapHeader )
404 {
405 for ( s32 i = 0; i < NumModels; i++)
406 {
407 Models[i].min[0] = os::Byteswap::byteswap(Models[i].min[0]);
408 Models[i].min[1] = os::Byteswap::byteswap(Models[i].min[1]);
409 Models[i].min[2] = os::Byteswap::byteswap(Models[i].min[2]);
410 Models[i].max[0] = os::Byteswap::byteswap(Models[i].max[0]);
411 Models[i].max[1] = os::Byteswap::byteswap(Models[i].max[1]);
412 Models[i].max[2] = os::Byteswap::byteswap(Models[i].max[2]);
413
414 Models[i].faceIndex = os::Byteswap::byteswap(Models[i].faceIndex);
415 Models[i].numOfFaces = os::Byteswap::byteswap(Models[i].numOfFaces);
416 Models[i].brushIndex = os::Byteswap::byteswap(Models[i].brushIndex);
417 Models[i].numOfBrushes = os::Byteswap::byteswap(Models[i].numOfBrushes);
418 }
419 }
420
421 BrushEntities = new SMesh*[NumModels];
422 }
423
424 /*!
425 */
loadMeshVerts(tBSPLump * l,io::IReadFile * file)426 void CQ3LevelMesh::loadMeshVerts(tBSPLump* l, io::IReadFile* file)
427 {
428 NumMeshVerts = l->length / sizeof(s32);
429 if (!NumMeshVerts)
430 return;
431 MeshVerts = new s32[NumMeshVerts];
432
433 file->seek(l->offset);
434 file->read(MeshVerts, l->length);
435
436 if ( LoadParam.swapHeader )
437 {
438 for (int i=0;i<NumMeshVerts;i++)
439 MeshVerts[i] = os::Byteswap::byteswap(MeshVerts[i]);
440 }
441 }
442
443 /*!
444 */
loadBrushes(tBSPLump * l,io::IReadFile * file)445 void CQ3LevelMesh::loadBrushes(tBSPLump* l, io::IReadFile* file)
446 {
447 // ignore
448 }
449
450 /*!
451 */
loadBrushSides(tBSPLump * l,io::IReadFile * file)452 void CQ3LevelMesh::loadBrushSides(tBSPLump* l, io::IReadFile* file)
453 {
454 // ignore
455 }
456
457 /*!
458 */
loadLeafBrushes(tBSPLump * l,io::IReadFile * file)459 void CQ3LevelMesh::loadLeafBrushes(tBSPLump* l, io::IReadFile* file)
460 {
461 // ignore
462 }
463
464 /*!
465 */
isQ3WhiteSpace(const u8 symbol)466 inline bool isQ3WhiteSpace( const u8 symbol )
467 {
468 return symbol == ' ' || symbol == '\t' || symbol == '\r';
469 }
470
471 /*!
472 */
isQ3ValidName(const u8 symbol)473 inline bool isQ3ValidName( const u8 symbol )
474 {
475 return (symbol >= 'a' && symbol <= 'z' ) ||
476 (symbol >= 'A' && symbol <= 'Z' ) ||
477 (symbol >= '0' && symbol <= '9' ) ||
478 (symbol == '/' || symbol == '_' || symbol == '.' );
479 }
480
481 /*!
482 */
parser_nextToken()483 void CQ3LevelMesh::parser_nextToken()
484 {
485 u8 symbol;
486
487 Parser.token = "";
488 Parser.tokenresult = Q3_TOKEN_UNRESOLVED;
489
490 // skip white space
491 do
492 {
493 if ( Parser.index >= Parser.sourcesize )
494 {
495 Parser.tokenresult = Q3_TOKEN_EOF;
496 return;
497 }
498
499 symbol = Parser.source [ Parser.index ];
500 Parser.index += 1;
501 } while ( isQ3WhiteSpace( symbol ) );
502
503 // first symbol, one symbol
504 switch ( symbol )
505 {
506 case 0:
507 Parser.tokenresult = Q3_TOKEN_EOF;
508 return;
509
510 case '/':
511 // comment or divide
512 if ( Parser.index >= Parser.sourcesize )
513 {
514 Parser.tokenresult = Q3_TOKEN_EOF;
515 return;
516 }
517 symbol = Parser.source [ Parser.index ];
518 Parser.index += 1;
519 if ( isQ3WhiteSpace( symbol ) )
520 {
521 Parser.tokenresult = Q3_TOKEN_MATH_DIVIDE;
522 return;
523 }
524 else
525 if ( symbol == '*' )
526 {
527 // C-style comment in quake?
528 }
529 else
530 if ( symbol == '/' )
531 {
532 // skip to eol
533 do
534 {
535 if ( Parser.index >= Parser.sourcesize )
536 {
537 Parser.tokenresult = Q3_TOKEN_EOF;
538 return;
539 }
540 symbol = Parser.source [ Parser.index ];
541 Parser.index += 1;
542 } while ( symbol != '\n' );
543 Parser.tokenresult = Q3_TOKEN_COMMENT;
544 return;
545 }
546 // take /[name] as valid token..?!?!?. mhmm, maybe
547 break;
548
549 case '\n':
550 Parser.tokenresult = Q3_TOKEN_EOL;
551 return;
552 case '{':
553 Parser.tokenresult = Q3_TOKEN_START_LIST;
554 return;
555 case '}':
556 Parser.tokenresult = Q3_TOKEN_END_LIST;
557 return;
558
559 case '"':
560 // string literal
561 do
562 {
563 if ( Parser.index >= Parser.sourcesize )
564 {
565 Parser.tokenresult = Q3_TOKEN_EOF;
566 return;
567 }
568 symbol = Parser.source [ Parser.index ];
569 Parser.index += 1;
570 if ( symbol != '"' )
571 Parser.token.append( symbol );
572 } while ( symbol != '"' );
573 Parser.tokenresult = Q3_TOKEN_ENTITY;
574 return;
575 }
576
577 // user identity
578 Parser.token.append( symbol );
579
580 // continue till whitespace
581 bool validName = true;
582 do
583 {
584 if ( Parser.index >= Parser.sourcesize )
585 {
586 Parser.tokenresult = Q3_TOKEN_EOF;
587 return;
588 }
589 symbol = Parser.source [ Parser.index ];
590
591 validName = isQ3ValidName( symbol );
592 if ( validName )
593 {
594 Parser.token.append( symbol );
595 Parser.index += 1;
596 }
597 } while ( validName );
598
599 Parser.tokenresult = Q3_TOKEN_TOKEN;
600 return;
601 }
602
603
604 /*
605 parse entity & shader
606 calls callback on content in {}
607 */
parser_parse(const void * data,const u32 size,CQ3LevelMesh::tParserCallback callback)608 void CQ3LevelMesh::parser_parse( const void * data, const u32 size, CQ3LevelMesh::tParserCallback callback )
609 {
610 Parser.source = static_cast<const c8*>(data);
611 Parser.sourcesize = size;
612 Parser.index = 0;
613
614 SVarGroupList *groupList;
615
616 s32 active;
617 s32 last;
618
619 SVariable entity ( "" );
620
621 groupList = new SVarGroupList();
622
623 groupList->VariableGroup.push_back( SVarGroup() );
624 active = last = 0;
625
626 do
627 {
628 parser_nextToken();
629
630 switch ( Parser.tokenresult )
631 {
632 case Q3_TOKEN_START_LIST:
633 {
634 //stack = core::min_( stack + 1, 7 );
635
636 groupList->VariableGroup.push_back( SVarGroup() );
637 last = active;
638 active = groupList->VariableGroup.size() - 1;
639 entity.clear();
640 } break;
641
642 // a unregisterd variable is finished
643 case Q3_TOKEN_EOL:
644 {
645 if ( entity.isValid() )
646 {
647 groupList->VariableGroup[active].Variable.push_back( entity );
648 entity.clear();
649 }
650 } break;
651
652 case Q3_TOKEN_TOKEN:
653 case Q3_TOKEN_ENTITY:
654 {
655 Parser.token.make_lower();
656
657 // store content based on line-delemiter
658 if ( 0 == entity.isValid() )
659 {
660 entity.name = Parser.token;
661 entity.content = "";
662
663 }
664 else
665 {
666 if ( entity.content.size() )
667 {
668 entity.content += " ";
669 }
670 entity.content += Parser.token;
671 }
672 } break;
673
674 case Q3_TOKEN_END_LIST:
675 {
676 //stack = core::max_( stack - 1, 0 );
677
678 // close tag for first
679 if ( active == 1 )
680 {
681 (this->*callback)( groupList, Q3_TOKEN_END_LIST );
682
683 // new group
684 groupList->drop();
685 groupList = new SVarGroupList();
686 groupList->VariableGroup.push_back( SVarGroup() );
687 last = 0;
688 }
689
690 active = last;
691 entity.clear();
692
693 } break;
694
695 default:
696 break;
697 }
698
699 } while ( Parser.tokenresult != Q3_TOKEN_EOF );
700
701 (this->*callback)( groupList, Q3_TOKEN_EOF );
702
703 groupList->drop();
704 }
705
706
707 /*
708 this loader applies only textures for stage 1 & 2
709 */
setShaderFogMaterial(video::SMaterial & material,const tBSPFace * face) const710 s32 CQ3LevelMesh::setShaderFogMaterial( video::SMaterial &material, const tBSPFace * face ) const
711 {
712 material.MaterialType = video::EMT_SOLID;
713 material.Wireframe = false;
714 material.Lighting = false;
715 material.BackfaceCulling = false;
716 material.setTexture(0, 0);
717 material.setTexture(1, 0);
718 material.setTexture(2, 0);
719 material.setTexture(3, 0);
720 material.ZBuffer = video::ECFN_LESSEQUAL;
721 material.ZWriteEnable = false;
722 material.MaterialTypeParam = 0.f;
723
724 s32 shaderState = -1;
725
726 if ( (u32) face->fogNum < FogMap.size() )
727 {
728 material.setTexture(0, FogMap [ face->fogNum ].Texture);
729 shaderState = FogMap [ face->fogNum ].ShaderID;
730 }
731
732 return shaderState;
733
734 }
735 /*
736 this loader applies only textures for stage 1 & 2
737 */
setShaderMaterial(video::SMaterial & material,const tBSPFace * face) const738 s32 CQ3LevelMesh::setShaderMaterial( video::SMaterial &material, const tBSPFace * face ) const
739 {
740 material.MaterialType = video::EMT_SOLID;
741 material.Wireframe = false;
742 material.Lighting = false;
743 material.BackfaceCulling = true;
744 material.setTexture(0, 0);
745 material.setTexture(1, 0);
746 material.setTexture(2, 0);
747 material.setTexture(3, 0);
748 material.ZBuffer = video::ECFN_LESSEQUAL;
749 material.ZWriteEnable = true;
750 material.MaterialTypeParam = 0.f;
751
752 s32 shaderState = -1;
753
754 if ( face->textureID >= 0 && face->textureID < (s32)Tex.size() )
755 {
756 material.setTexture(0, Tex [ face->textureID ].Texture);
757 shaderState = Tex [ face->textureID ].ShaderID;
758 }
759
760 if ( face->lightmapID >= 0 && face->lightmapID < (s32)Lightmap.size() )
761 {
762 material.setTexture(1, Lightmap [ face->lightmapID ]);
763 material.MaterialType = LoadParam.defaultLightMapMaterial;
764 }
765
766 // store shader ID
767 material.MaterialTypeParam2 = (f32) shaderState;
768
769 const IShader *shader = getShader(shaderState);
770 if ( 0 == shader )
771 return shaderState;
772
773 return shaderState;
774
775 #if 0
776 const SVarGroup *group;
777
778
779 // generic
780 group = shader->getGroup( 1 );
781 if ( group )
782 {
783 material.BackfaceCulling = getCullingFunction( group->get( "cull" ) );
784
785 if ( group->isDefined( "surfaceparm", "nolightmap" ) )
786 {
787 material.MaterialType = video::EMT_SOLID;
788 material.setTexture(1, 0);
789 }
790
791 }
792
793 // try to get the best of the 8 texture stages..
794
795 // texture 1, texture 2
796 u32 startPos;
797 for ( s32 g = 2; g <= 3; ++g )
798 {
799 group = shader->getGroup( g );
800 if ( 0 == group )
801 continue;
802
803 startPos = 0;
804
805 if ( group->isDefined( "depthwrite" ) )
806 {
807 material.ZWriteEnable = true;
808 }
809
810 SBlendFunc blendfunc ( LoadParam.defaultModulate );
811 getBlendFunc( group->get( "blendfunc" ), blendfunc );
812 getBlendFunc( group->get( "alphafunc" ), blendfunc );
813
814 if ( 0 == LoadParam.alpharef &&
815 ( blendfunc.type == video::EMT_TRANSPARENT_ALPHA_CHANNEL ||
816 blendfunc.type == video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF
817 )
818 )
819 {
820 blendfunc.type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
821 blendfunc.param0 = 0.f;
822 }
823
824 material.MaterialType = blendfunc.type;
825 material.MaterialTypeParam = blendfunc.param0;
826
827 // try if we can match better
828 shaderState |= (material.MaterialType == video::EMT_SOLID ) ? 0x00020000 : 0;
829 }
830
831 //material.BackfaceCulling = false;
832
833 if ( shader->VarGroup->VariableGroup.size() <= 4 )
834 {
835 shaderState |= 0x00010000;
836 }
837
838 material.MaterialTypeParam2 = (f32) shaderState;
839 return shaderState;
840 #endif
841 }
842
843 /*!
844 Internal function to build a mesh.
845 */
buildMesh(s32 num)846 scene::SMesh** CQ3LevelMesh::buildMesh(s32 num)
847 {
848 scene::SMesh** newmesh = new SMesh *[quake3::E_Q3_MESH_SIZE];
849
850 s32 i, j, k,s;
851
852 for (i = 0; i < E_Q3_MESH_SIZE; i++)
853 {
854 newmesh[i] = new SMesh();
855 }
856
857 s32 *index;
858
859 video::S3DVertex2TCoords temp[3];
860 video::SMaterial material;
861 video::SMaterial material2;
862
863 SToBuffer item [ E_Q3_MESH_SIZE ];
864 u32 itemSize;
865
866 for (i = Models[num].faceIndex; i < Models[num].numOfFaces + Models[num].faceIndex; ++i)
867 {
868 const tBSPFace * face = Faces + i;
869
870 s32 shaderState = setShaderMaterial( material, face );
871 itemSize = 0;
872
873 const IShader *shader = getShader(shaderState);
874
875 if ( face->fogNum >= 0 )
876 {
877 setShaderFogMaterial ( material2, face );
878 item[itemSize].index = E_Q3_MESH_FOG;
879 item[itemSize].takeVertexColor = 1;
880 itemSize += 1;
881 }
882
883 switch( face->type )
884 {
885 case 1: // normal polygons
886 case 2: // patches
887 case 3: // meshes
888 if ( 0 == shader )
889 {
890 if ( LoadParam.cleanUnResolvedMeshes || material.getTexture(0) )
891 {
892 item[itemSize].takeVertexColor = 1;
893 item[itemSize].index = E_Q3_MESH_GEOMETRY;
894 itemSize += 1;
895 }
896 else
897 {
898 item[itemSize].takeVertexColor = 1;
899 item[itemSize].index = E_Q3_MESH_UNRESOLVED;
900 itemSize += 1;
901 }
902 }
903 else
904 {
905 item[itemSize].takeVertexColor = 1;
906 item[itemSize].index = E_Q3_MESH_ITEMS;
907 itemSize += 1;
908 }
909 break;
910
911 case 4: // billboards
912 //item[itemSize].takeVertexColor = 1;
913 //item[itemSize].index = E_Q3_MESH_ITEMS;
914 //itemSize += 1;
915 break;
916
917 }
918
919 for ( u32 g = 0; g != itemSize; ++g )
920 {
921 scene::SMeshBufferLightMap* buffer = 0;
922
923 if ( item[g].index == E_Q3_MESH_GEOMETRY )
924 {
925 if ( 0 == item[g].takeVertexColor )
926 {
927 item[g].takeVertexColor = material.getTexture(0) == 0 || material.getTexture(1) == 0;
928 }
929
930 if (Faces[i].lightmapID < -1 || Faces[i].lightmapID > NumLightMaps-1)
931 {
932 Faces[i].lightmapID = -1;
933 }
934
935 #if 0
936 // there are lightmapsids and textureid with -1
937 const s32 tmp_index = ((Faces[i].lightmapID+1) * (NumTextures+1)) + (Faces[i].textureID+1);
938 buffer = (SMeshBufferLightMap*) newmesh[E_Q3_MESH_GEOMETRY]->getMeshBuffer(tmp_index);
939 buffer->setHardwareMappingHint ( EHM_STATIC );
940 buffer->getMaterial() = material;
941 #endif
942 }
943
944 // Construct a unique mesh for each shader or combine meshbuffers for same shader
945 if ( 0 == buffer )
946 {
947
948 if ( LoadParam.mergeShaderBuffer == 1 )
949 {
950 // combine
951 buffer = (SMeshBufferLightMap*) newmesh[ item[g].index ]->getMeshBuffer(
952 item[g].index != E_Q3_MESH_FOG ? material : material2 );
953 }
954
955 // create a seperate mesh buffer
956 if ( 0 == buffer )
957 {
958 buffer = new scene::SMeshBufferLightMap();
959 newmesh[ item[g].index ]->addMeshBuffer( buffer );
960 buffer->drop();
961 buffer->getMaterial() = item[g].index != E_Q3_MESH_FOG ? material : material2;
962 if ( item[g].index == E_Q3_MESH_GEOMETRY )
963 buffer->setHardwareMappingHint ( EHM_STATIC );
964 }
965 }
966
967
968 switch(Faces[i].type)
969 {
970 case 4: // billboards
971 break;
972 case 2: // patches
973 createCurvedSurface_bezier( buffer, i,
974 LoadParam.patchTesselation,
975 item[g].takeVertexColor
976 );
977 break;
978
979 case 1: // normal polygons
980 case 3: // mesh vertices
981 index = MeshVerts + face->meshVertIndex;
982 k = buffer->getVertexCount();
983
984 // reallocate better if many small meshes are used
985 s = buffer->getIndexCount()+face->numMeshVerts;
986 if ( buffer->Indices.allocated_size () < (u32) s )
987 {
988 if ( buffer->Indices.allocated_size () > 0 &&
989 face->numMeshVerts < 20 && NumFaces > 1000
990 )
991 {
992 s = buffer->getIndexCount() + (NumFaces >> 3 * face->numMeshVerts );
993 }
994 buffer->Indices.reallocate( s);
995 }
996
997 for ( j = 0; j < face->numMeshVerts; ++j )
998 {
999 buffer->Indices.push_back( k + index [j] );
1000 }
1001
1002 s = k+face->numOfVerts;
1003 if ( buffer->Vertices.allocated_size () < (u32) s )
1004 {
1005 if ( buffer->Indices.allocated_size () > 0 &&
1006 face->numOfVerts < 20 && NumFaces > 1000
1007 )
1008 {
1009 s = buffer->getIndexCount() + (NumFaces >> 3 * face->numOfVerts );
1010 }
1011 buffer->Vertices.reallocate( s);
1012 }
1013 for ( j = 0; j != face->numOfVerts; ++j )
1014 {
1015 copy( &temp[0], &Vertices[ j + face->vertexIndex ], item[g].takeVertexColor );
1016 buffer->Vertices.push_back( temp[0] );
1017 }
1018 break;
1019
1020 } // end switch
1021 }
1022 }
1023
1024 return newmesh;
1025 }
1026
1027 /*!
1028 */
solveTJunction()1029 void CQ3LevelMesh::solveTJunction()
1030 {
1031 }
1032
1033 /*!
1034 constructs a mesh from the quake 3 level file.
1035 */
constructMesh()1036 void CQ3LevelMesh::constructMesh()
1037 {
1038 if ( LoadParam.verbose > 0 )
1039 {
1040 LoadParam.startTime = os::Timer::getRealTime();
1041
1042 if ( LoadParam.verbose > 1 )
1043 {
1044 snprintf( buf, sizeof ( buf ),
1045 "quake3::constructMesh start to create %d faces, %d vertices,%d mesh vertices",
1046 NumFaces,
1047 NumVertices,
1048 NumMeshVerts
1049 );
1050 os::Printer::log(buf, ELL_INFORMATION);
1051 }
1052
1053 }
1054
1055 s32 i, j;
1056
1057 // First the main level
1058 SMesh **tmp = buildMesh(0);
1059
1060 for (i = 0; i < E_Q3_MESH_SIZE; i++)
1061 {
1062 Mesh[i] = tmp[i];
1063 }
1064 delete [] tmp;
1065
1066 // Then the brush entities
1067
1068 for (i = 1; i < NumModels; i++)
1069 {
1070 tmp = buildMesh(i);
1071 BrushEntities[i] = tmp[0];
1072
1073 // We only care about the main geometry here
1074 for (j = 1; j < E_Q3_MESH_SIZE; j++)
1075 {
1076 tmp[j]->drop();
1077 }
1078 delete [] tmp;
1079 }
1080
1081 if ( LoadParam.verbose > 0 )
1082 {
1083 LoadParam.endTime = os::Timer::getRealTime();
1084
1085 snprintf( buf, sizeof ( buf ),
1086 "quake3::constructMesh needed %04d ms to create %d faces, %d vertices,%d mesh vertices",
1087 LoadParam.endTime - LoadParam.startTime,
1088 NumFaces,
1089 NumVertices,
1090 NumMeshVerts
1091 );
1092 os::Printer::log(buf, ELL_INFORMATION);
1093 }
1094
1095 }
1096
1097
copy(video::S3DVertex2TCoords & dest) const1098 void CQ3LevelMesh::S3DVertex2TCoords_64::copy( video::S3DVertex2TCoords &dest ) const
1099 {
1100 #if defined (TJUNCTION_SOLVER_ROUND)
1101 dest.Pos.X = core::round_( (f32) Pos.X );
1102 dest.Pos.Y = core::round_( (f32) Pos.Y );
1103 dest.Pos.Z = core::round_( (f32) Pos.Z );
1104 #elif defined (TJUNCTION_SOLVER_0125)
1105 dest.Pos.X = (f32) ( floor ( Pos.X * 8.f + 0.5 ) * 0.125 );
1106 dest.Pos.Y = (f32) ( floor ( Pos.Y * 8.f + 0.5 ) * 0.125 );
1107 dest.Pos.Z = (f32) ( floor ( Pos.Z * 8.f + 0.5 ) * 0.125 );
1108 #else
1109 dest.Pos.X = (f32) Pos.X;
1110 dest.Pos.Y = (f32) Pos.Y;
1111 dest.Pos.Z = (f32) Pos.Z;
1112 #endif
1113
1114 dest.Normal.X = (f32) Normal.X;
1115 dest.Normal.Y = (f32) Normal.Y;
1116 dest.Normal.Z = (f32) Normal.Z;
1117 dest.Normal.normalize();
1118
1119 dest.Color = Color.toSColor();
1120
1121 dest.TCoords.X = (f32) TCoords.X;
1122 dest.TCoords.Y = (f32) TCoords.Y;
1123
1124 dest.TCoords2.X = (f32) TCoords2.X;
1125 dest.TCoords2.Y = (f32) TCoords2.Y;
1126 }
1127
1128
copy(S3DVertex2TCoords_64 * dest,const tBSPVertex * source,s32 vertexcolor) const1129 void CQ3LevelMesh::copy( S3DVertex2TCoords_64 * dest, const tBSPVertex * source, s32 vertexcolor ) const
1130 {
1131 #if defined (TJUNCTION_SOLVER_ROUND)
1132 dest->Pos.X = core::round_( source->vPosition[0] );
1133 dest->Pos.Y = core::round_( source->vPosition[2] );
1134 dest->Pos.Z = core::round_( source->vPosition[1] );
1135 #elif defined (TJUNCTION_SOLVER_0125)
1136 dest->Pos.X = (f32) ( floor ( source->vPosition[0] * 8.f + 0.5 ) * 0.125 );
1137 dest->Pos.Y = (f32) ( floor ( source->vPosition[2] * 8.f + 0.5 ) * 0.125 );
1138 dest->Pos.Z = (f32) ( floor ( source->vPosition[1] * 8.f + 0.5 ) * 0.125 );
1139 #else
1140 dest->Pos.X = source->vPosition[0];
1141 dest->Pos.Y = source->vPosition[2];
1142 dest->Pos.Z = source->vPosition[1];
1143 #endif
1144
1145 dest->Normal.X = source->vNormal[0];
1146 dest->Normal.Y = source->vNormal[2];
1147 dest->Normal.Z = source->vNormal[1];
1148 dest->Normal.normalize();
1149
1150 dest->TCoords.X = source->vTextureCoord[0];
1151 dest->TCoords.Y = source->vTextureCoord[1];
1152 dest->TCoords2.X = source->vLightmapCoord[0];
1153 dest->TCoords2.Y = source->vLightmapCoord[1];
1154
1155 if ( vertexcolor )
1156 {
1157 //u32 a = core::s32_min( source->color[3] * LoadParam.defaultModulate, 255 );
1158 u32 a = source->color[3];
1159 u32 r = core::s32_min( source->color[0] * LoadParam.defaultModulate, 255 );
1160 u32 g = core::s32_min( source->color[1] * LoadParam.defaultModulate, 255 );
1161 u32 b = core::s32_min( source->color[2] * LoadParam.defaultModulate, 255 );
1162
1163 dest->Color.set(a * 1.f/255.f, r * 1.f/255.f,
1164 g * 1.f/255.f, b * 1.f/255.f);
1165 }
1166 else
1167 {
1168 dest->Color.set( 1.f, 1.f, 1.f, 1.f );
1169 }
1170 }
1171
1172
copy(video::S3DVertex2TCoords * dest,const tBSPVertex * source,s32 vertexcolor) const1173 inline void CQ3LevelMesh::copy( video::S3DVertex2TCoords * dest, const tBSPVertex * source, s32 vertexcolor ) const
1174 {
1175 #if defined (TJUNCTION_SOLVER_ROUND)
1176 dest->Pos.X = core::round_( source->vPosition[0] );
1177 dest->Pos.Y = core::round_( source->vPosition[2] );
1178 dest->Pos.Z = core::round_( source->vPosition[1] );
1179 #elif defined (TJUNCTION_SOLVER_0125)
1180 dest->Pos.X = (f32) ( floor ( source->vPosition[0] * 8.f + 0.5 ) * 0.125 );
1181 dest->Pos.Y = (f32) ( floor ( source->vPosition[2] * 8.f + 0.5 ) * 0.125 );
1182 dest->Pos.Z = (f32) ( floor ( source->vPosition[1] * 8.f + 0.5 ) * 0.125 );
1183 #else
1184 dest->Pos.X = source->vPosition[0];
1185 dest->Pos.Y = source->vPosition[2];
1186 dest->Pos.Z = source->vPosition[1];
1187 #endif
1188
1189 dest->Normal.X = source->vNormal[0];
1190 dest->Normal.Y = source->vNormal[2];
1191 dest->Normal.Z = source->vNormal[1];
1192 dest->Normal.normalize();
1193
1194 dest->TCoords.X = source->vTextureCoord[0];
1195 dest->TCoords.Y = source->vTextureCoord[1];
1196 dest->TCoords2.X = source->vLightmapCoord[0];
1197 dest->TCoords2.Y = source->vLightmapCoord[1];
1198
1199 if ( vertexcolor )
1200 {
1201 //u32 a = core::s32_min( source->color[3] * LoadParam.defaultModulate, 255 );
1202 u32 a = source->color[3];
1203 u32 r = core::s32_min( source->color[0] * LoadParam.defaultModulate, 255 );
1204 u32 g = core::s32_min( source->color[1] * LoadParam.defaultModulate, 255 );
1205 u32 b = core::s32_min( source->color[2] * LoadParam.defaultModulate, 255 );
1206
1207 dest->Color.set(a << 24 | r << 16 | g << 8 | b);
1208 }
1209 else
1210 {
1211 dest->Color.set(0xFFFFFFFF);
1212 }
1213 }
1214
1215
tesselate(s32 level)1216 void CQ3LevelMesh::SBezier::tesselate( s32 level )
1217 {
1218 //Calculate how many vertices across/down there are
1219 s32 j, k;
1220
1221 column[0].set_used( level + 1 );
1222 column[1].set_used( level + 1 );
1223 column[2].set_used( level + 1 );
1224
1225 const f64 w = 0.0 + (1.0 / (f64) level );
1226
1227 //Tesselate along the columns
1228 for( j = 0; j <= level; ++j)
1229 {
1230 const f64 f = w * (f64) j;
1231
1232 column[0][j] = control[0].getInterpolated_quadratic(control[3], control[6], f );
1233 column[1][j] = control[1].getInterpolated_quadratic(control[4], control[7], f );
1234 column[2][j] = control[2].getInterpolated_quadratic(control[5], control[8], f );
1235 }
1236
1237 const u32 idx = Patch->Vertices.size();
1238 Patch->Vertices.reallocate(idx+level*level);
1239 //Tesselate across the rows to get final vertices
1240 video::S3DVertex2TCoords v;
1241 S3DVertex2TCoords_64 f;
1242 for( j = 0; j <= level; ++j)
1243 {
1244 for( k = 0; k <= level; ++k)
1245 {
1246 f = column[0][j].getInterpolated_quadratic(column[1][j], column[2][j], w * (f64) k);
1247 f.copy( v );
1248 Patch->Vertices.push_back( v );
1249 }
1250 }
1251
1252 Patch->Indices.reallocate(Patch->Indices.size()+6*level*level);
1253 // connect
1254 for( j = 0; j < level; ++j)
1255 {
1256 for( k = 0; k < level; ++k)
1257 {
1258 const s32 inx = idx + ( k * ( level + 1 ) ) + j;
1259
1260 Patch->Indices.push_back( inx + 0 );
1261 Patch->Indices.push_back( inx + (level + 1 ) + 0 );
1262 Patch->Indices.push_back( inx + (level + 1 ) + 1 );
1263
1264 Patch->Indices.push_back( inx + 0 );
1265 Patch->Indices.push_back( inx + (level + 1 ) + 1 );
1266 Patch->Indices.push_back( inx + 1 );
1267 }
1268 }
1269 }
1270
1271
1272 /*!
1273 no subdivision
1274 */
createCurvedSurface_nosubdivision(SMeshBufferLightMap * meshBuffer,s32 faceIndex,s32 patchTesselation,s32 storevertexcolor)1275 void CQ3LevelMesh::createCurvedSurface_nosubdivision(SMeshBufferLightMap* meshBuffer,
1276 s32 faceIndex,
1277 s32 patchTesselation,
1278 s32 storevertexcolor)
1279 {
1280 tBSPFace * face = &Faces[faceIndex];
1281 u32 j,k,m;
1282
1283 // number of control points across & up
1284 const u32 controlWidth = face->size[0];
1285 const u32 controlHeight = face->size[1];
1286 if ( 0 == controlWidth || 0 == controlHeight )
1287 return;
1288
1289 video::S3DVertex2TCoords v;
1290
1291 m = meshBuffer->Vertices.size();
1292 meshBuffer->Vertices.reallocate(m+controlHeight * controlWidth);
1293 for ( j = 0; j!= controlHeight * controlWidth; ++j )
1294 {
1295 copy( &v, &Vertices [ face->vertexIndex + j ], storevertexcolor );
1296 meshBuffer->Vertices.push_back( v );
1297 }
1298
1299 meshBuffer->Indices.reallocate(meshBuffer->Indices.size()+6*(controlHeight-1) * (controlWidth-1));
1300 for ( j = 0; j!= controlHeight - 1; ++j )
1301 {
1302 for ( k = 0; k!= controlWidth - 1; ++k )
1303 {
1304 meshBuffer->Indices.push_back( m + k + 0 );
1305 meshBuffer->Indices.push_back( m + k + controlWidth + 0 );
1306 meshBuffer->Indices.push_back( m + k + controlWidth + 1 );
1307
1308 meshBuffer->Indices.push_back( m + k + 0 );
1309 meshBuffer->Indices.push_back( m + k + controlWidth + 1 );
1310 meshBuffer->Indices.push_back( m + k + 1 );
1311 }
1312 m += controlWidth;
1313 }
1314 }
1315
1316
1317 /*!
1318 */
createCurvedSurface_bezier(SMeshBufferLightMap * meshBuffer,s32 faceIndex,s32 patchTesselation,s32 storevertexcolor)1319 void CQ3LevelMesh::createCurvedSurface_bezier(SMeshBufferLightMap* meshBuffer,
1320 s32 faceIndex,
1321 s32 patchTesselation,
1322 s32 storevertexcolor)
1323 {
1324
1325 tBSPFace * face = &Faces[faceIndex];
1326 u32 j,k;
1327
1328 // number of control points across & up
1329 const u32 controlWidth = face->size[0];
1330 const u32 controlHeight = face->size[1];
1331
1332 if ( 0 == controlWidth || 0 == controlHeight )
1333 return;
1334
1335 // number of biquadratic patches
1336 const u32 biquadWidth = (controlWidth - 1)/2;
1337 const u32 biquadHeight = (controlHeight -1)/2;
1338
1339 if ( LoadParam.verbose > 1 )
1340 {
1341 LoadParam.startTime = os::Timer::getRealTime();
1342 }
1343
1344 // Create space for a temporary array of the patch's control points
1345 core::array<S3DVertex2TCoords_64> controlPoint;
1346 controlPoint.set_used( controlWidth * controlHeight );
1347
1348 for( j = 0; j < controlPoint.size(); ++j)
1349 {
1350 copy( &controlPoint[j], &Vertices [ face->vertexIndex + j ], storevertexcolor );
1351 }
1352
1353 // create a temporary patch
1354 Bezier.Patch = new scene::SMeshBufferLightMap();
1355
1356 //Loop through the biquadratic patches
1357 for( j = 0; j < biquadHeight; ++j)
1358 {
1359 for( k = 0; k < biquadWidth; ++k)
1360 {
1361 // set up this patch
1362 const s32 inx = j*controlWidth*2 + k*2;
1363
1364 // setup bezier control points for this patch
1365 Bezier.control[0] = controlPoint[ inx + 0];
1366 Bezier.control[1] = controlPoint[ inx + 1];
1367 Bezier.control[2] = controlPoint[ inx + 2];
1368 Bezier.control[3] = controlPoint[ inx + controlWidth + 0 ];
1369 Bezier.control[4] = controlPoint[ inx + controlWidth + 1 ];
1370 Bezier.control[5] = controlPoint[ inx + controlWidth + 2 ];
1371 Bezier.control[6] = controlPoint[ inx + controlWidth * 2 + 0];
1372 Bezier.control[7] = controlPoint[ inx + controlWidth * 2 + 1];
1373 Bezier.control[8] = controlPoint[ inx + controlWidth * 2 + 2];
1374
1375 Bezier.tesselate( patchTesselation );
1376 }
1377 }
1378
1379 // stitch together with existing geometry
1380 // TODO: only border needs to be checked
1381 const u32 bsize = Bezier.Patch->getVertexCount();
1382 const u32 msize = meshBuffer->getVertexCount();
1383 /*
1384 for ( j = 0; j!= bsize; ++j )
1385 {
1386 const core::vector3df &v = Bezier.Patch->Vertices[j].Pos;
1387
1388 for ( k = 0; k!= msize; ++k )
1389 {
1390 const core::vector3df &m = meshBuffer->Vertices[k].Pos;
1391
1392 if ( !v.equals( m, tolerance ) )
1393 continue;
1394
1395 meshBuffer->Vertices[k].Pos = v;
1396 //Bezier.Patch->Vertices[j].Pos = m;
1397 }
1398 }
1399 */
1400
1401 // add Patch to meshbuffer
1402 meshBuffer->Vertices.reallocate(msize+bsize);
1403 for ( j = 0; j!= bsize; ++j )
1404 {
1405 meshBuffer->Vertices.push_back( Bezier.Patch->Vertices[j] );
1406 }
1407
1408 // add indices to meshbuffer
1409 meshBuffer->Indices.reallocate(meshBuffer->getIndexCount()+Bezier.Patch->getIndexCount());
1410 for ( j = 0; j!= Bezier.Patch->getIndexCount(); ++j )
1411 {
1412 meshBuffer->Indices.push_back( msize + Bezier.Patch->Indices[j] );
1413 }
1414
1415 delete Bezier.Patch;
1416
1417 if ( LoadParam.verbose > 1 )
1418 {
1419 LoadParam.endTime = os::Timer::getRealTime();
1420
1421 snprintf( buf, sizeof ( buf ),
1422 "quake3::createCurvedSurface_bezier needed %04d ms to create bezier patch.(%dx%d)",
1423 LoadParam.endTime - LoadParam.startTime,
1424 biquadWidth,
1425 biquadHeight
1426 );
1427 os::Printer::log(buf, ELL_INFORMATION);
1428 }
1429
1430 }
1431
1432
1433
1434 /*!
1435 Loads entities from file
1436 */
getConfiguration(io::IReadFile * file)1437 void CQ3LevelMesh::getConfiguration( io::IReadFile* file )
1438 {
1439 tBSPLump l;
1440 l.offset = file->getPos();
1441 l.length = file->getSize ();
1442
1443 core::array<u8> entity;
1444 entity.set_used( l.length + 2 );
1445 entity[l.length + 1 ] = 0;
1446
1447 file->seek(l.offset);
1448 file->read( entity.pointer(), l.length);
1449
1450 parser_parse( entity.pointer(), l.length, &CQ3LevelMesh::scriptcallback_config );
1451
1452 if ( Entity.size () )
1453 Entity.getLast().name = file->getFileName();
1454 }
1455
1456
1457 //! get's an interface to the entities
getEntityList()1458 tQ3EntityList & CQ3LevelMesh::getEntityList()
1459 {
1460 // Entity.sort();
1461 return Entity;
1462 }
1463
1464 //! returns the requested brush entity
getBrushEntityMesh(s32 num) const1465 IMesh* CQ3LevelMesh::getBrushEntityMesh(s32 num) const
1466 {
1467 if (num < 1 || num >= NumModels)
1468 return 0;
1469
1470 return BrushEntities[num];
1471 }
1472
1473 //! returns the requested brush entity
getBrushEntityMesh(quake3::IEntity & ent) const1474 IMesh* CQ3LevelMesh::getBrushEntityMesh(quake3::IEntity &ent) const
1475 {
1476 // This is a helper function to parse the entity,
1477 // so you don't have to.
1478
1479 s32 num;
1480
1481 const quake3::SVarGroup* group = ent.getGroup(1);
1482 const core::stringc& modnum = group->get("model");
1483
1484 if (!group->isDefined("model"))
1485 return 0;
1486
1487 const char *temp = modnum.c_str() + 1; // We skip the first character.
1488 num = core::strtol10(temp);
1489
1490 return getBrushEntityMesh(num);
1491 }
1492
1493
1494 /*!
1495 */
getShader(u32 index) const1496 const IShader * CQ3LevelMesh::getShader(u32 index) const
1497 {
1498 index &= 0xFFFF;
1499
1500 if ( index < Shader.size() )
1501 {
1502 return &Shader[index];
1503 }
1504
1505 return 0;
1506 }
1507
1508
1509 /*!
1510 loads the shader definition
1511 */
getShader(const c8 * filename,bool fileNameIsValid)1512 const IShader* CQ3LevelMesh::getShader( const c8 * filename, bool fileNameIsValid )
1513 {
1514 core::stringc searchName ( filename );
1515
1516 IShader search;
1517 search.name = searchName;
1518 search.name.replace( '\\', '/' );
1519 search.name.make_lower();
1520
1521
1522 core::stringc message;
1523 s32 index;
1524
1525 //! is Shader already in cache?
1526 index = Shader.linear_search( search );
1527 if ( index >= 0 )
1528 {
1529 if ( LoadParam.verbose > 1 )
1530 {
1531 message = searchName + " found " + Shader[index].name;
1532 os::Printer::log("quake3:getShader", message.c_str(), ELL_INFORMATION);
1533 }
1534
1535 return &Shader[index];
1536 }
1537
1538 io::path loadFile;
1539
1540 if ( !fileNameIsValid )
1541 {
1542 // extract the shader name from the last path component in filename
1543 // "scripts/[name].shader"
1544 core::stringc cut( search.name );
1545
1546 s32 end = cut.findLast( '/' );
1547 s32 start = cut.findLast( '/', end - 1 );
1548
1549 loadFile = LoadParam.scriptDir;
1550 loadFile.append( cut.subString( start, end - start ) );
1551 loadFile.append( ".shader" );
1552 }
1553 else
1554 {
1555 loadFile = search.name;
1556 }
1557
1558 // already loaded the file ?
1559 index = ShaderFile.binary_search( loadFile );
1560 if ( index >= 0 )
1561 return 0;
1562
1563 // add file to loaded files
1564 ShaderFile.push_back( loadFile );
1565
1566 if ( !FileSystem->existFile( loadFile.c_str() ) )
1567 {
1568 if ( LoadParam.verbose > 1 )
1569 {
1570 message = loadFile + " for " + searchName + " failed ";
1571 os::Printer::log("quake3:getShader", message.c_str(), ELL_INFORMATION);
1572 }
1573 return 0;
1574 }
1575
1576 if ( LoadParam.verbose )
1577 {
1578 message = loadFile + " for " + searchName;
1579 os::Printer::log("quake3:getShader Load shader", message.c_str(), ELL_INFORMATION);
1580 }
1581
1582
1583 io::IReadFile *file = FileSystem->createAndOpenFile( loadFile.c_str() );
1584 if ( file )
1585 {
1586 getShader ( file );
1587 file->drop ();
1588 }
1589
1590
1591 // search again
1592 index = Shader.linear_search( search );
1593 return index >= 0 ? &Shader[index] : 0;
1594 }
1595
1596 /*!
1597 loads the shader definition
1598 */
getShader(io::IReadFile * file)1599 void CQ3LevelMesh::getShader( io::IReadFile* file )
1600 {
1601 if ( 0 == file )
1602 return;
1603
1604 // load script
1605 core::array<u8> script;
1606 const long len = file->getSize();
1607
1608 script.set_used( len + 2 );
1609
1610 file->seek( 0 );
1611 file->read( script.pointer(), len );
1612 script[ len + 1 ] = 0;
1613
1614 // start a parser instance
1615 parser_parse( script.pointer(), len, &CQ3LevelMesh::scriptcallback_shader );
1616 }
1617
1618
1619 //! adding default shaders
InitShader()1620 void CQ3LevelMesh::InitShader()
1621 {
1622 ReleaseShader();
1623
1624 IShader element;
1625
1626 SVarGroup group;
1627 SVariable variable ( "noshader" );
1628
1629 group.Variable.push_back( variable );
1630
1631 element.VarGroup = new SVarGroupList();
1632 element.VarGroup->VariableGroup.push_back( group );
1633 element.VarGroup->VariableGroup.push_back( SVarGroup() );
1634 element.name = element.VarGroup->VariableGroup[0].Variable[0].name;
1635 element.ID = Shader.size();
1636 Shader.push_back( element );
1637
1638 if ( LoadParam.loadAllShaders )
1639 {
1640 io::EFileSystemType current = FileSystem->setFileListSystem ( io::FILESYSTEM_VIRTUAL );
1641 io::path save = FileSystem->getWorkingDirectory();
1642
1643 io::path newDir;
1644 newDir = "/";
1645 newDir += LoadParam.scriptDir;
1646 newDir += "/";
1647 FileSystem->changeWorkingDirectoryTo ( newDir.c_str() );
1648
1649 core::stringc s;
1650 io::IFileList *fileList = FileSystem->createFileList ();
1651 for (u32 i=0; i< fileList->getFileCount(); ++i)
1652 {
1653 s = fileList->getFullFileName(i);
1654 if ( s.find ( ".shader" ) >= 0 )
1655 {
1656 if ( 0 == LoadParam.loadSkyShader && s.find ( "sky.shader" ) >= 0 )
1657 {
1658 }
1659 else
1660 {
1661 getShader ( s.c_str () );
1662 }
1663 }
1664 }
1665 fileList->drop ();
1666
1667 FileSystem->changeWorkingDirectoryTo ( save );
1668 FileSystem->setFileListSystem ( current );
1669 }
1670 }
1671
1672
1673 //! script callback for shaders
1674 //! i'm having troubles with the reference counting, during callback.. resorting..
ReleaseShader()1675 void CQ3LevelMesh::ReleaseShader()
1676 {
1677 for ( u32 i = 0; i!= Shader.size(); ++i )
1678 {
1679 Shader[i].VarGroup->drop();
1680 }
1681 Shader.clear();
1682 ShaderFile.clear();
1683 }
1684
1685
1686 /*!
1687 */
ReleaseEntity()1688 void CQ3LevelMesh::ReleaseEntity()
1689 {
1690 for ( u32 i = 0; i!= Entity.size(); ++i )
1691 {
1692 Entity[i].VarGroup->drop();
1693 }
1694 Entity.clear();
1695 }
1696
1697
1698 // config in simple (quake3) and advanced style
scriptcallback_config(SVarGroupList * & grouplist,eToken token)1699 void CQ3LevelMesh::scriptcallback_config( SVarGroupList *& grouplist, eToken token )
1700 {
1701 IShader element;
1702
1703 if ( token == Q3_TOKEN_END_LIST )
1704 {
1705 if ( 0 == grouplist->VariableGroup[0].Variable.size() )
1706 return;
1707
1708 element.name = grouplist->VariableGroup[0].Variable[0].name;
1709 }
1710 else
1711 {
1712 if ( grouplist->VariableGroup.size() != 2 )
1713 return;
1714
1715 element.name = "configuration";
1716 }
1717
1718 grouplist->grab();
1719 element.VarGroup = grouplist;
1720 element.ID = Entity.size();
1721 Entity.push_back( element );
1722 }
1723
1724
1725 // entity only has only one valid level.. and no assoziative name..
scriptcallback_entity(SVarGroupList * & grouplist,eToken token)1726 void CQ3LevelMesh::scriptcallback_entity( SVarGroupList *& grouplist, eToken token )
1727 {
1728 if ( token != Q3_TOKEN_END_LIST || grouplist->VariableGroup.size() != 2 )
1729 return;
1730
1731 grouplist->grab();
1732
1733 IEntity element;
1734 element.VarGroup = grouplist;
1735 element.ID = Entity.size();
1736 element.name = grouplist->VariableGroup[1].get( "classname" );
1737
1738
1739 Entity.push_back( element );
1740 }
1741
1742
1743 //!. script callback for shaders
scriptcallback_shader(SVarGroupList * & grouplist,eToken token)1744 void CQ3LevelMesh::scriptcallback_shader( SVarGroupList *& grouplist,eToken token )
1745 {
1746 if ( token != Q3_TOKEN_END_LIST || grouplist->VariableGroup[0].Variable.size()==0)
1747 return;
1748
1749
1750 IShader element;
1751
1752 grouplist->grab();
1753 element.VarGroup = grouplist;
1754 element.name = element.VarGroup->VariableGroup[0].Variable[0].name;
1755 element.ID = Shader.size();
1756 /*
1757 core::stringc s;
1758 dumpShader ( s, &element );
1759 printf ( s.c_str () );
1760 */
1761 Shader.push_back( element );
1762 }
1763
1764
1765 /*!
1766 delete all buffers without geometry in it.
1767 */
cleanMeshes()1768 void CQ3LevelMesh::cleanMeshes()
1769 {
1770 if ( 0 == LoadParam.cleanUnResolvedMeshes )
1771 return;
1772
1773 s32 i;
1774
1775 // First the main level
1776 for (i = 0; i < E_Q3_MESH_SIZE; i++)
1777 {
1778 bool texture0important = ( i == 0 );
1779
1780 cleanMesh(Mesh[i], texture0important);
1781 }
1782
1783 // Then the brush entities
1784 for (i = 1; i < NumModels; i++)
1785 {
1786 cleanMesh(BrushEntities[i], true);
1787 }
1788 }
1789
cleanMesh(SMesh * m,const bool texture0important)1790 void CQ3LevelMesh::cleanMesh(SMesh *m, const bool texture0important)
1791 {
1792 // delete all buffers without geometry in it.
1793 u32 run = 0;
1794 u32 remove = 0;
1795
1796 IMeshBuffer *b;
1797
1798 run = 0;
1799 remove = 0;
1800
1801 if ( LoadParam.verbose > 0 )
1802 {
1803 LoadParam.startTime = os::Timer::getRealTime();
1804 if ( LoadParam.verbose > 1 )
1805 {
1806 snprintf( buf, sizeof ( buf ),
1807 "quake3::cleanMeshes start for %d meshes",
1808 m->MeshBuffers.size()
1809 );
1810 os::Printer::log(buf, ELL_INFORMATION);
1811 }
1812 }
1813
1814 u32 i = 0;
1815 s32 blockstart = -1;
1816 s32 blockcount = 0;
1817
1818 while( i < m->MeshBuffers.size())
1819 {
1820 run += 1;
1821
1822 b = m->MeshBuffers[i];
1823
1824 if ( b->getVertexCount() == 0 || b->getIndexCount() == 0 ||
1825 ( texture0important && b->getMaterial().getTexture(0) == 0 )
1826 )
1827 {
1828 if ( blockstart < 0 )
1829 {
1830 blockstart = i;
1831 blockcount = 0;
1832 }
1833 blockcount += 1;
1834 i += 1;
1835
1836 // delete Meshbuffer
1837 i -= 1;
1838 remove += 1;
1839 b->drop();
1840 m->MeshBuffers.erase(i);
1841 }
1842 else
1843 {
1844 // clean blockwise
1845 if ( blockstart >= 0 )
1846 {
1847 if ( LoadParam.verbose > 1 )
1848 {
1849 snprintf( buf, sizeof ( buf ),
1850 "quake3::cleanMeshes cleaning mesh %d %d size",
1851 blockstart,
1852 blockcount
1853 );
1854 os::Printer::log(buf, ELL_INFORMATION);
1855 }
1856 blockstart = -1;
1857 }
1858 i += 1;
1859 }
1860 }
1861
1862 if ( LoadParam.verbose > 0 )
1863 {
1864 LoadParam.endTime = os::Timer::getRealTime();
1865 snprintf( buf, sizeof ( buf ),
1866 "quake3::cleanMeshes needed %04d ms to clean %d of %d meshes",
1867 LoadParam.endTime - LoadParam.startTime,
1868 remove,
1869 run
1870 );
1871 os::Printer::log(buf, ELL_INFORMATION);
1872 }
1873 }
1874
1875
1876 // recalculate bounding boxes
calcBoundingBoxes()1877 void CQ3LevelMesh::calcBoundingBoxes()
1878 {
1879 if ( LoadParam.verbose > 0 )
1880 {
1881 LoadParam.startTime = os::Timer::getRealTime();
1882
1883 if ( LoadParam.verbose > 1 )
1884 {
1885 snprintf( buf, sizeof ( buf ),
1886 "quake3::calcBoundingBoxes start create %d textures and %d lightmaps",
1887 NumTextures,
1888 NumLightMaps
1889 );
1890 os::Printer::log(buf, ELL_INFORMATION);
1891 }
1892 }
1893
1894 s32 g;
1895
1896 // create bounding box
1897 for ( g = 0; g != E_Q3_MESH_SIZE; ++g )
1898 {
1899 for ( u32 j=0; j < Mesh[g]->MeshBuffers.size(); ++j)
1900 {
1901 ((SMeshBufferLightMap*)Mesh[g]->MeshBuffers[j])->recalculateBoundingBox();
1902 }
1903
1904 Mesh[g]->recalculateBoundingBox();
1905 // Mesh[0] is the main bbox
1906 if (g!=0)
1907 Mesh[0]->BoundingBox.addInternalBox(Mesh[g]->getBoundingBox());
1908 }
1909
1910 for (g = 1; g < NumModels; g++)
1911 {
1912 for ( u32 j=0; j < BrushEntities[g]->MeshBuffers.size(); ++j)
1913 {
1914 ((SMeshBufferLightMap*)BrushEntities[g]->MeshBuffers[j])->
1915 recalculateBoundingBox();
1916 }
1917
1918 BrushEntities[g]->recalculateBoundingBox();
1919 }
1920
1921 if ( LoadParam.verbose > 0 )
1922 {
1923 LoadParam.endTime = os::Timer::getRealTime();
1924
1925 snprintf( buf, sizeof ( buf ),
1926 "quake3::calcBoundingBoxes needed %04d ms to create %d textures and %d lightmaps",
1927 LoadParam.endTime - LoadParam.startTime,
1928 NumTextures,
1929 NumLightMaps
1930 );
1931 os::Printer::log( buf, ELL_INFORMATION);
1932 }
1933 }
1934
1935
1936 //! loads the textures
loadTextures()1937 void CQ3LevelMesh::loadTextures()
1938 {
1939 if (!Driver)
1940 return;
1941
1942 if ( LoadParam.verbose > 0 )
1943 {
1944 LoadParam.startTime = os::Timer::getRealTime();
1945
1946 if ( LoadParam.verbose > 1 )
1947 {
1948 snprintf( buf, sizeof ( buf ),
1949 "quake3::loadTextures start create %d textures and %d lightmaps",
1950 NumTextures,
1951 NumLightMaps
1952 );
1953 os::Printer::log( buf, ELL_INFORMATION);
1954 }
1955 }
1956
1957 c8 lightmapname[255];
1958 s32 t;
1959
1960 // load lightmaps.
1961 Lightmap.set_used(NumLightMaps);
1962
1963 /*
1964 bool oldMipMapState = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
1965 Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
1966 */
1967 core::dimension2d<u32> lmapsize(128,128);
1968
1969 video::IImage* lmapImg;
1970 for ( t = 0; t < NumLightMaps ; ++t)
1971 {
1972 sprintf(lightmapname, "%s.lightmap.%d", LevelName.c_str(), t);
1973
1974 // lightmap is a CTexture::R8G8B8 format
1975 lmapImg = Driver->createImageFromData(
1976 video::ECF_R8G8B8, lmapsize,
1977 LightMaps[t].imageBits, false, true );
1978
1979 Lightmap[t] = Driver->addTexture( lightmapname, lmapImg );
1980 lmapImg->drop();
1981 }
1982
1983 // Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, oldMipMapState);
1984
1985 // load textures
1986 Tex.set_used( NumTextures );
1987
1988 const IShader* shader;
1989
1990 core::stringc list;
1991 io::path check;
1992 tTexArray textureArray;
1993
1994 // pre-load shaders
1995 for ( t=0; t< NumTextures; ++t)
1996 {
1997 shader = getShader(Textures[t].strName, false);
1998 }
1999
2000 for ( t=0; t< NumTextures; ++t)
2001 {
2002 Tex[t].ShaderID = -1;
2003 Tex[t].Texture = 0;
2004
2005 list = "";
2006
2007 // get a shader ( if one exists )
2008 shader = getShader( Textures[t].strName, false);
2009 if ( shader )
2010 {
2011 Tex[t].ShaderID = shader->ID;
2012
2013 // if texture name == stage1 Texture map
2014 const SVarGroup * group;
2015
2016 group = shader->getGroup( 2 );
2017 if ( group )
2018 {
2019 if ( core::cutFilenameExtension( check, group->get( "map" ) ) == Textures[t].strName )
2020 {
2021 list += check;
2022 }
2023 else
2024 if ( check == "$lightmap" )
2025 {
2026 // we check if lightmap is in stage 1 and texture in stage 2
2027 group = shader->getGroup( 3 );
2028 if ( group )
2029 list += group->get( "map" );
2030 }
2031 }
2032 }
2033 else
2034 {
2035 // no shader, take it
2036 list += Textures[t].strName;
2037 }
2038
2039 u32 pos = 0;
2040 getTextures( textureArray, list, pos, FileSystem, Driver );
2041
2042 Tex[t].Texture = textureArray[0];
2043 }
2044
2045 if ( LoadParam.verbose > 0 )
2046 {
2047 LoadParam.endTime = os::Timer::getRealTime();
2048
2049 snprintf( buf, sizeof ( buf ),
2050 "quake3::loadTextures needed %04d ms to create %d textures and %d lightmaps",
2051 LoadParam.endTime - LoadParam.startTime,
2052 NumTextures,
2053 NumLightMaps
2054 );
2055 os::Printer::log( buf, ELL_INFORMATION);
2056 }
2057 }
2058
2059
2060 //! Returns an axis aligned bounding box of the mesh.
getBoundingBox() const2061 const core::aabbox3d<f32>& CQ3LevelMesh::getBoundingBox() const
2062 {
2063 return Mesh[0]->getBoundingBox();
2064 }
2065
2066
setBoundingBox(const core::aabbox3df & box)2067 void CQ3LevelMesh::setBoundingBox(const core::aabbox3df& box)
2068 {
2069 Mesh[0]->setBoundingBox(box);
2070 }
2071
2072
2073 //! Returns the type of the animated mesh.
getMeshType() const2074 E_ANIMATED_MESH_TYPE CQ3LevelMesh::getMeshType() const
2075 {
2076 return scene::EAMT_BSP;
2077 }
2078
2079 } // end namespace scene
2080 } // end namespace irr
2081
2082 #endif // _IRR_COMPILE_WITH_BSP_LOADER_
2083