| 1 | /* |
|---|
| 2 | ================================================================================= |
|---|
| 3 | This file is part of Cafu, the open-source game engine and graphics engine |
|---|
| 4 | for multiplayer, cross-platform, real-time 3D action. |
|---|
| 5 | Copyright (C) 2002-2012 Carsten Fuchs Software. |
|---|
| 6 | |
|---|
| 7 | Cafu is free software: you can redistribute it and/or modify it under the terms |
|---|
| 8 | of the GNU General Public License as published by the Free Software Foundation, |
|---|
| 9 | either version 3 of the License, or (at your option) any later version. |
|---|
| 10 | |
|---|
| 11 | Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; |
|---|
| 12 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
|---|
| 13 | PURPOSE. See the GNU General Public License for more details. |
|---|
| 14 | |
|---|
| 15 | You should have received a copy of the GNU General Public License |
|---|
| 16 | along with Cafu. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 17 | |
|---|
| 18 | For support and more information about Cafu, visit us at <http://www.cafu.de>. |
|---|
| 19 | ================================================================================= |
|---|
| 20 | */ |
|---|
| 21 | |
|---|
| 22 | /******************/ |
|---|
| 23 | /*** Load World ***/ |
|---|
| 24 | /******************/ |
|---|
| 25 | |
|---|
| 26 | #include <iostream> |
|---|
| 27 | #include "ClipSys/CollisionModel_static.hpp" |
|---|
| 28 | #include "ConsoleCommands/ConsoleWarningsOnly.hpp" |
|---|
| 29 | #include "TextParser/TextParser.hpp" |
|---|
| 30 | #include "SceneGraph/BezierPatchNode.hpp" |
|---|
| 31 | #include "SceneGraph/BspTreeNode.hpp" |
|---|
| 32 | #include "SceneGraph/FaceNode.hpp" |
|---|
| 33 | #include "SceneGraph/TerrainNode.hpp" |
|---|
| 34 | #include "SceneGraph/PlantNode.hpp" |
|---|
| 35 | #include "SceneGraph/ModelNode.hpp" |
|---|
| 36 | #include "MapFile.hpp" |
|---|
| 37 | #include "Models/ModelManager.hpp" |
|---|
| 38 | #include "Plants/PlantDescrMan.hpp" |
|---|
| 39 | |
|---|
| 40 | |
|---|
| 41 | using namespace cf; |
|---|
| 42 | |
|---|
| 43 | |
|---|
| 44 | void MapFileSanityCheck(const ArrayT<MapFileEntityT>& MFEntityList) |
|---|
| 45 | { |
|---|
| 46 | unsigned long NrOfWorldSpawnEntities =0; |
|---|
| 47 | unsigned long NrOfInfoPlayerStartEntities=0; |
|---|
| 48 | |
|---|
| 49 | for (unsigned long EntityNr=0; EntityNr<MFEntityList.Size(); EntityNr++) |
|---|
| 50 | { |
|---|
| 51 | const MapFileEntityT& E=MFEntityList[EntityNr]; |
|---|
| 52 | const std::map<std::string, std::string>::const_iterator It=E.MFProperties.find("classname"); |
|---|
| 53 | |
|---|
| 54 | if (It==E.MFProperties.end()) Error("\"classname\" property not found in entity %lu.", EntityNr); |
|---|
| 55 | |
|---|
| 56 | // If we found the 'classname worldspawn' property pair, we count the occurence, because there must only be exactly one such entity. |
|---|
| 57 | // Additionally, our map file spec. implies that there MUST be a 'mapfile_version XX' pair following the 'classname worldspawn' pair. |
|---|
| 58 | if (It->second=="worldspawn") |
|---|
| 59 | { |
|---|
| 60 | if (EntityNr!=0) Error("Entity %u is the 'worldspawn' entity, but entity 0 is expected to be the only 'worldspawn' entity.", EntityNr); |
|---|
| 61 | if (E.MFProperties.size()==1) Error("Entity %u is the worldspawn entity, but has only one PPair (missing mapfile_version).", EntityNr); |
|---|
| 62 | |
|---|
| 63 | // These two issues are directly checked while loading. |
|---|
| 64 | // The pair also must not be precisely at second position (index 1) anymore. |
|---|
| 65 | // if (E.MFPropPairs[1].Key!="mapfile_version") Error("Entity %u: Second PPair key is not 'mapfile_version'.", EntityNr); |
|---|
| 66 | // if (atoi(E.MFPropPairs[1].Value.c_str())!=9)) Error("Bad map file version: Expected %s, got %s.", "9", E.MFPropPairs[1].Value.c_str()); |
|---|
| 67 | |
|---|
| 68 | if (E.MFBrushes.Size()<4) |
|---|
| 69 | { |
|---|
| 70 | Console->Print(cf::va("This map only has %lu brush%s, but CSG requires at least 4 (better 6).\n", E.MFBrushes.Size(), E.MFBrushes.Size()==1 ? "" : "es")); |
|---|
| 71 | Console->Print("That means that the minimum of geometry you have to provide is a small room,\n"); |
|---|
| 72 | Console->Print("consisting of 4 walls, the floor, and the ceiling.\n"); |
|---|
| 73 | Console->Print("That in turn means that you need at least 6 brushes to create a \"closed room\".\n"); |
|---|
| 74 | Console->Print("(The minimum is 4 brushes for a \"closed pyramid\". :-)\n"); |
|---|
| 75 | Error("Too few brushes."); |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | NrOfWorldSpawnEntities++; |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | // If this is a 'classname info_player_start' entity, we count the occurence, because we insist that there is at least one such entity. |
|---|
| 82 | if (It->second=="info_player_start") |
|---|
| 83 | NrOfInfoPlayerStartEntities++; |
|---|
| 84 | |
|---|
| 85 | // No further checks should be necessary for now. We can always ignore invalid property pairs and/or fill in default values. |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | if (NrOfWorldSpawnEntities !=1) Error("Found %u worldspawn entities, expected exactly 1.", NrOfWorldSpawnEntities); |
|---|
| 89 | if (NrOfInfoPlayerStartEntities==0) Error("Found no info_player_start entities at all, expected at least 1.", NrOfInfoPlayerStartEntities); |
|---|
| 90 | } |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | // Nimmt ein Token, das aus drei durch Leerzeichen voneinander getrennten Zahlen besteht und gibt sie als VectorT zurück. |
|---|
| 94 | VectorT GetVectorFromTripleToken(const std::string& TripleToken) |
|---|
| 95 | { |
|---|
| 96 | VectorT V; |
|---|
| 97 | std::istringstream iss(TripleToken); |
|---|
| 98 | |
|---|
| 99 | iss >> V.x >> V.y >> V.z; // Bank, Heading and Pitch. |
|---|
| 100 | |
|---|
| 101 | return V; |
|---|
| 102 | } |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | /* OBSOLETE - CaWE saves the cmap files now immediately right. |
|---|
| 106 | // Nimmt ein Token, das aus drei durch Leerzeichen voneinander getrennten Zahlen besteht, die als Winkel interpretiert werden, |
|---|
| 107 | // und gibt die dazugehörige Richtung als VectorT zurück. |
|---|
| 108 | VectorT GetDirFromTripleAngleToken(const char* TripleToken) |
|---|
| 109 | { |
|---|
| 110 | VectorT V; |
|---|
| 111 | std::istringstream iss(TripleToken); |
|---|
| 112 | |
|---|
| 113 | iss >> V.x >> V.y >> V.z; |
|---|
| 114 | |
|---|
| 115 | VectorT D(0.0, 1.0, 0.0); |
|---|
| 116 | |
|---|
| 117 | // Sigh. All this angle related stuff *REALLY* must be checked: |
|---|
| 118 | // Which angles rotates about which axis, and in what order? |
|---|
| 119 | // Also wrt. the BBs of static detail models! |
|---|
| 120 | D=D.GetRotX(V.x); |
|---|
| 121 | D=D.GetRotZ(V.y); |
|---|
| 122 | |
|---|
| 123 | return normalize(D, 0.0); |
|---|
| 124 | } */ |
|---|
| 125 | |
|---|
| 126 | |
|---|
| 127 | // Computes the brush polygons from a MapFileBrushT. |
|---|
| 128 | void ComputeBrushPolys(const MapFileBrushT& MFBrush, ArrayT< Polygon3T<double> >& BrushPolys, const unsigned long EntityNr, const unsigned long BrushNr) |
|---|
| 129 | { |
|---|
| 130 | // Konstruiere die Polygone des Brushes. |
|---|
| 131 | for (unsigned long MFPlaneNr=0; MFPlaneNr<MFBrush.MFPlanes.Size(); MFPlaneNr++) |
|---|
| 132 | { |
|---|
| 133 | BrushPolys.PushBackEmpty(); |
|---|
| 134 | BrushPolys[BrushPolys.Size()-1].Plane=MFBrush.MFPlanes[MFPlaneNr].Plane; |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | |
|---|
| 138 | Polygon3T<double>::Complete(BrushPolys, MapT::RoundEpsilon); |
|---|
| 139 | |
|---|
| 140 | |
|---|
| 141 | // Prüfe die Gültigkeit der konstruierten Polygone. |
|---|
| 142 | // Eine explizite Gültigkeitsprüfung ist sinnvoll und notwendig um sicherzustellen, daß wir mit "sauberen" Eingabedaten anfangen! |
|---|
| 143 | for (unsigned long MFPlaneNr=0; MFPlaneNr<BrushPolys.Size(); MFPlaneNr++) |
|---|
| 144 | if (!BrushPolys[MFPlaneNr].IsValid(MapT::RoundEpsilon, MapT::MinVertexDist)) |
|---|
| 145 | { |
|---|
| 146 | Console->Print("\n"); |
|---|
| 147 | for (unsigned long PlaneNr=0; PlaneNr<MFBrush.MFPlanes.Size(); PlaneNr++) |
|---|
| 148 | std::cout << convertToString(MFBrush.MFPlanes[PlaneNr].Plane) << " " |
|---|
| 149 | << MFBrush.MFPlanes[PlaneNr].Material->Name << "\n"; |
|---|
| 150 | |
|---|
| 151 | Console->Print("\n"); |
|---|
| 152 | for (unsigned long PlaneNr=0; PlaneNr<BrushPolys.Size(); PlaneNr++) |
|---|
| 153 | { |
|---|
| 154 | std::cout << "Plane " << convertToString(BrushPolys[PlaneNr].Plane) << ", Vertices "; |
|---|
| 155 | |
|---|
| 156 | for (unsigned long VertexNr=0; VertexNr<BrushPolys[PlaneNr].Vertices.Size(); VertexNr++) |
|---|
| 157 | std::cout << convertToString(BrushPolys[PlaneNr].Vertices[VertexNr]) << " "; |
|---|
| 158 | |
|---|
| 159 | std::cout << "\n"; |
|---|
| 160 | } |
|---|
| 161 | |
|---|
| 162 | Error("Entity #%u, brush #%u: polygon #%u is invalid.", EntityNr, BrushNr, MFPlaneNr); |
|---|
| 163 | } |
|---|
| 164 | } |
|---|
| 165 | |
|---|
| 166 | |
|---|
| 167 | void ComputeBrushFaces(const MapFileBrushT& MFBrush, WorldT& World, cf::SceneGraph::BspTreeNodeT* BspTree, |
|---|
| 168 | ArrayT<VectorT>& DrawWorldOutsidePointSamples, const unsigned long EntityNr, const unsigned long BrushNr) |
|---|
| 169 | { |
|---|
| 170 | // Compute the overall clip flags (combined from the faces) of the brush. |
|---|
| 171 | unsigned long CombinedClipFlags_Ored =0; |
|---|
| 172 | unsigned long CombinedClipFlags_Anded=0xFFFFFFFF; |
|---|
| 173 | |
|---|
| 174 | for (unsigned long MFPlaneNr=0; MFPlaneNr<MFBrush.MFPlanes.Size(); MFPlaneNr++) |
|---|
| 175 | { |
|---|
| 176 | const MapFilePlaneT& MFPlane=MFBrush.MFPlanes[MFPlaneNr]; |
|---|
| 177 | |
|---|
| 178 | CombinedClipFlags_Ored |=MFPlane.Material->ClipFlags; |
|---|
| 179 | CombinedClipFlags_Anded&=MFPlane.Material->ClipFlags; |
|---|
| 180 | |
|---|
| 181 | // Print a warning if a materials stops portals, but not players and monsters. |
|---|
| 182 | if ((MFPlane.Material->ClipFlags & MaterialT::Clip_BspPortals)!=0 && |
|---|
| 183 | !((MFPlane.Material->ClipFlags & MaterialT::Clip_Players)!=0 && (MFPlane.Material->ClipFlags & MaterialT::Clip_Monsters)!=0)) |
|---|
| 184 | Console->Warning("Material "+MFPlane.Material->Name+" stops portals, but not players and monsters!\n"); |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| 187 | // The materials of all faces should have the same value for the MaterialT::Clip_BspPortals flag - all on or all off. |
|---|
| 188 | // Having mixed flags is not really a problem, but can lead to some "unusual" results later during Portalization, |
|---|
| 189 | // e.g. when a cube that is floating in mid-air has 5 sides with and one side without the bspPortals flag set. |
|---|
| 190 | if ((CombinedClipFlags_Ored & MaterialT::Clip_BspPortals)!=(CombinedClipFlags_Anded & MaterialT::Clip_BspPortals)) |
|---|
| 191 | Console->Warning(cf::va("Entity %lu, brush %lu: Faces have materials with mismatching \"bspPortal\" clip flags.\n", EntityNr, BrushNr)); |
|---|
| 192 | |
|---|
| 193 | |
|---|
| 194 | // Compute the brush polygons. |
|---|
| 195 | ArrayT< Polygon3T<double> > BrushPolys; |
|---|
| 196 | ComputeBrushPolys(MFBrush, BrushPolys, EntityNr, BrushNr); |
|---|
| 197 | |
|---|
| 198 | |
|---|
| 199 | // Compute the center of the brush, to be used for leak detection later. |
|---|
| 200 | // The algorithm is probably not 100% correct, in the sense that the computed center |
|---|
| 201 | // might be different from mathematically correct center, but that's negligible. |
|---|
| 202 | VectorT BrushCenter; |
|---|
| 203 | unsigned long AverageCount=0; |
|---|
| 204 | |
|---|
| 205 | for (unsigned long MFPlaneNr=0; MFPlaneNr<BrushPolys.Size(); MFPlaneNr++) |
|---|
| 206 | for (unsigned long VertexNr=0; VertexNr<BrushPolys[MFPlaneNr].Vertices.Size(); VertexNr++) |
|---|
| 207 | { |
|---|
| 208 | BrushCenter+=BrushPolys[MFPlaneNr].Vertices[VertexNr]; |
|---|
| 209 | AverageCount++; |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | BrushCenter=scale(BrushCenter, 1.0/double(AverageCount)); |
|---|
| 213 | |
|---|
| 214 | // Include a small sanity check to make sure that the 'BrushCenter' is really inside the brush (think of rounding errors...). |
|---|
| 215 | for (unsigned long MFPlaneNr=0; MFPlaneNr<BrushPolys.Size(); MFPlaneNr++) |
|---|
| 216 | if (BrushPolys[MFPlaneNr].Plane.GetDistance(BrushCenter) > -MapT::RoundEpsilon) |
|---|
| 217 | Error("Entity #%u, brush #%u: BrushCenter is outside brush.", EntityNr, BrushNr); |
|---|
| 218 | |
|---|
| 219 | // If all faces of the brush are solid for BSP portals (unlike e.g. glass, there cannot be a portal that sees into the brush), |
|---|
| 220 | // consider the inside of the brush as "solid" and "outside of the visible world", and thus collect an outside point sample. |
|---|
| 221 | if (CombinedClipFlags_Anded & MaterialT::Clip_BspPortals) DrawWorldOutsidePointSamples.PushBack(BrushCenter); |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | for (unsigned long MFPlaneNr=0; MFPlaneNr<MFBrush.MFPlanes.Size(); MFPlaneNr++) |
|---|
| 225 | { |
|---|
| 226 | const MapFilePlaneT& MFPlane=MFBrush.MFPlanes[MFPlaneNr]; |
|---|
| 227 | |
|---|
| 228 | if (((MFPlane.Material->AmbientShaderName=="none" && MFPlane.Material->LightShaderName=="none") || MFPlane.Material->NoDraw) && |
|---|
| 229 | (MFPlane.Material->ClipFlags & MaterialT::Clip_BspPortals)==0) |
|---|
| 230 | { |
|---|
| 231 | // A face enters the draw BSP tree only if it is visible (like walls, glass, water) or stops the BSP flood-fill |
|---|
| 232 | // (like invisible "caulk" materials), or both (like most of the normal walls). |
|---|
| 233 | // Faces with materials that don't draw at all and are not "caulk" (invisible materials that nontheless stop portals) |
|---|
| 234 | // can be omitted from the BSP tree. As the BSP tree is only used for drawing, this prevents unnecessary leaves and thus complexity. |
|---|
| 235 | continue; |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | cf::SceneGraph::FaceNodeT::TexInfoT TI; |
|---|
| 239 | |
|---|
| 240 | TI.U =MFPlane.U.AsVectorOfFloat(); |
|---|
| 241 | TI.V =MFPlane.V.AsVectorOfFloat(); |
|---|
| 242 | TI.OffsetU=float(MFPlane.ShiftU); |
|---|
| 243 | TI.OffsetV=float(MFPlane.ShiftV); |
|---|
| 244 | |
|---|
| 245 | BspTree->FaceChildren.PushBack(new cf::SceneGraph::FaceNodeT(World.LightMapMan, World.SHLMapMan, BrushPolys[MFPlaneNr], MFPlane.Material, TI)); |
|---|
| 246 | } |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | |
|---|
| 250 | // Ließt ein MapFile, das die der Version entsprechenden "MapFile Specifications" erfüllen muß, in die World ein. |
|---|
| 251 | // Dabei werden folgende Komponenten der World modifiziert (ausgefüllt, u.U. nur teilweise): |
|---|
| 252 | // Map.Faces, Map.TexInfos, Map.PointLights, InfoPlayerStarts und GameEntities. |
|---|
| 253 | void LoadWorld(const char* LoadName, const std::string& GameDirectory, ModelManagerT& ModelMan, WorldT& World, ArrayT<VectorT>& DrawWorldOutsidePointSamples) |
|---|
| 254 | { |
|---|
| 255 | World.PlantDescrMan.SetModDir(GameDirectory); |
|---|
| 256 | |
|---|
| 257 | Console->Print(cf::va("\n*** Load World %s ***\n", LoadName)); |
|---|
| 258 | |
|---|
| 259 | |
|---|
| 260 | // Parse all map entities into the MFEntityList. |
|---|
| 261 | ArrayT<MapFileEntityT> MFEntityList; |
|---|
| 262 | TextParserT TP(LoadName, "({})"); |
|---|
| 263 | |
|---|
| 264 | try |
|---|
| 265 | { |
|---|
| 266 | MapFileReadHeader(TP); |
|---|
| 267 | |
|---|
| 268 | while (!TP.IsAtEOF()) |
|---|
| 269 | { |
|---|
| 270 | MFEntityList.PushBack(MapFileEntityT(MFEntityList.Size(), TP)); |
|---|
| 271 | } |
|---|
| 272 | } |
|---|
| 273 | catch (const TextParserT::ParseError&) |
|---|
| 274 | { |
|---|
| 275 | Error("Problem with parsing the map near byte %lu (%.3f%%) of the file.", TP.GetReadPosByte(), TP.GetReadPosPercent()*100.0); |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | |
|---|
| 279 | // Perform certain sanity checks on the map (MFEntityList), look into the function for details. |
|---|
| 280 | MapFileSanityCheck(MFEntityList); |
|---|
| 281 | |
|---|
| 282 | |
|---|
| 283 | // 'func_group' entities are just for editor convenience, thus toss all their brushes into the 'worldspawn' entity. |
|---|
| 284 | // 'func_wall' and 'func_water' entities exist for historic reasons (render walls specially, treat water specially), |
|---|
| 285 | // but are largely obsolete now with the Material System and Clip System and should probably be removed from |
|---|
| 286 | // the maps and the EntityClassDefs.lua files. |
|---|
| 287 | // Assumptions: |
|---|
| 288 | // 1.) Entity 0 is the 'worldspawn' entity. |
|---|
| 289 | // 2.) Each entity has the "classname" property. |
|---|
| 290 | for (unsigned long EntityNr=1; EntityNr<MFEntityList.Size(); EntityNr++) |
|---|
| 291 | { |
|---|
| 292 | const std::string EntClassName=MFEntityList[EntityNr].MFProperties["classname"]; |
|---|
| 293 | |
|---|
| 294 | if (EntClassName=="func_group" || EntClassName=="func_wall" || EntClassName=="func_water") |
|---|
| 295 | { |
|---|
| 296 | // Copy all brushes of this entity into the 'worldspawn' entity. |
|---|
| 297 | for (unsigned long BrushNr=0; BrushNr<MFEntityList[EntityNr].MFBrushes.Size(); BrushNr++) |
|---|
| 298 | MFEntityList[0].MFBrushes.PushBack(MFEntityList[EntityNr].MFBrushes[BrushNr]); |
|---|
| 299 | |
|---|
| 300 | // Copy all bezier patches of this entity into the 'worldspawn' entity. |
|---|
| 301 | for (unsigned long BPNr=0; BPNr<MFEntityList[EntityNr].MFPatches.Size(); BPNr++) |
|---|
| 302 | MFEntityList[0].MFPatches.PushBack(MFEntityList[EntityNr].MFPatches[BPNr]); |
|---|
| 303 | |
|---|
| 304 | // Delete this entity. |
|---|
| 305 | MFEntityList[EntityNr]=MFEntityList[MFEntityList.Size()-1]; |
|---|
| 306 | MFEntityList.DeleteBack(); |
|---|
| 307 | EntityNr--; |
|---|
| 308 | } |
|---|
| 309 | } |
|---|
| 310 | |
|---|
| 311 | |
|---|
| 312 | // *** HACK HACK HACK HACK *** (In fact, two hacks total.) |
|---|
| 313 | // |
|---|
| 314 | // In TechDemo.cmap, "light" entities sometimes have Bezier Patches (and brushes?). |
|---|
| 315 | // However, Cafu can currently not render them as such, thus let's move them into the "worldspawn" entity. |
|---|
| 316 | for (unsigned long EntityNr=1; EntityNr<MFEntityList.Size(); EntityNr++) |
|---|
| 317 | { |
|---|
| 318 | MapFileEntityT& E=MFEntityList[EntityNr]; |
|---|
| 319 | |
|---|
| 320 | if (E.MFProperties["classname"]=="light") |
|---|
| 321 | { |
|---|
| 322 | // Move all brushes of this entity into the 'worldspawn' entity. |
|---|
| 323 | for (unsigned long BrushNr=0; BrushNr<E.MFBrushes.Size(); BrushNr++) |
|---|
| 324 | MFEntityList[0].MFBrushes.PushBack(E.MFBrushes[BrushNr]); |
|---|
| 325 | E.MFBrushes.Clear(); |
|---|
| 326 | |
|---|
| 327 | // Move all bezier patches of this entity into the 'worldspawn' entity. |
|---|
| 328 | for (unsigned long BPNr=0; BPNr<E.MFPatches.Size(); BPNr++) |
|---|
| 329 | MFEntityList[0].MFPatches.PushBack(E.MFPatches[BPNr]); |
|---|
| 330 | E.MFPatches.Clear(); |
|---|
| 331 | |
|---|
| 332 | #if 1 |
|---|
| 333 | // Delete this entity. |
|---|
| 334 | MFEntityList[EntityNr]=MFEntityList[MFEntityList.Size()-1]; |
|---|
| 335 | MFEntityList.DeleteBack(); |
|---|
| 336 | EntityNr--; |
|---|
| 337 | #else |
|---|
| 338 | // Now the 2nd part of the hack: Convert the "light" entity into a "PointLightSource" entity, |
|---|
| 339 | // it will be included with the other regular game entities below. |
|---|
| 340 | // NOTE: "PointLight" and "PointLightSource" are NOT THE SAME! |
|---|
| 341 | bool HaveColor =false; |
|---|
| 342 | bool HaveRadius=false; |
|---|
| 343 | |
|---|
| 344 | for (unsigned long PPairNr=1; PPairNr<E.MFPropPairs.Size(); PPairNr++) |
|---|
| 345 | if (E.MFPropPairs[PPairNr].Key=="_color") HaveColor=true; |
|---|
| 346 | else if (E.MFPropPairs[PPairNr].Key=="light_radius") HaveRadius=true; |
|---|
| 347 | |
|---|
| 348 | if (!HaveColor || !HaveRadius) continue; |
|---|
| 349 | |
|---|
| 350 | // Okay, it's a point light source. |
|---|
| 351 | E.MFPropPairs[0].Value="PointLightSource"; |
|---|
| 352 | |
|---|
| 353 | for (unsigned long PPairNr=1; PPairNr<E.MFPropPairs.Size(); PPairNr++) |
|---|
| 354 | { |
|---|
| 355 | if (E.MFPropPairs[PPairNr].Key=="_color") |
|---|
| 356 | { |
|---|
| 357 | const Vector3dT LightColor=GetVectorFromTripleToken(E.MFPropPairs[PPairNr].Value); |
|---|
| 358 | char str[256]; |
|---|
| 359 | |
|---|
| 360 | sprintf(str, "%i %i %i", int(LightColor.x*255), int(LightColor.y*255), int(LightColor.z*255)); |
|---|
| 361 | |
|---|
| 362 | E.MFPropPairs[PPairNr].Key ="light_color_diff"; |
|---|
| 363 | E.MFPropPairs[PPairNr].Value=str; |
|---|
| 364 | |
|---|
| 365 | MapFilePropPairT SpecCol; |
|---|
| 366 | SpecCol.Key ="light_color_spec"; |
|---|
| 367 | SpecCol.Value=str; |
|---|
| 368 | E.MFPropPairs.PushBack(SpecCol); |
|---|
| 369 | } |
|---|
| 370 | else if (E.MFPropPairs[PPairNr].Key=="light_radius") |
|---|
| 371 | { |
|---|
| 372 | const Vector3dT LightRadius=GetVectorFromTripleToken(E.MFPropPairs[PPairNr].Value); |
|---|
| 373 | const double LightRAvg =(LightRadius.x+LightRadius.y+LightRadius.z)/3.0 * CA3DE_SCALE; |
|---|
| 374 | char str[256]; |
|---|
| 375 | |
|---|
| 376 | sprintf(str, "%i", int(LightRAvg)); |
|---|
| 377 | |
|---|
| 378 | E.MFPropPairs[PPairNr].Value=str; |
|---|
| 379 | } |
|---|
| 380 | else if (E.MFPropPairs[PPairNr].Key=="light_origin") |
|---|
| 381 | { |
|---|
| 382 | // Update our "origin" with this value?? |
|---|
| 383 | // E.MFPropPairs[OriginPPairNr].Value=E.MFPropPairs[PPairNr].Value; |
|---|
| 384 | } |
|---|
| 385 | else if (E.MFPropPairs[PPairNr].Key=="light_target" || E.MFPropPairs[PPairNr].Key=="light_up" || E.MFPropPairs[PPairNr].Key=="light_right") |
|---|
| 386 | { |
|---|
| 387 | Console->Warning("\"light\" entity %lu, mixed point and projected light keys?\n", EntityNr); |
|---|
| 388 | } |
|---|
| 389 | } |
|---|
| 390 | #endif |
|---|
| 391 | } |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | |
|---|
| 395 | // Finally, fill-in our World data structures! |
|---|
| 396 | for (unsigned long EntityNr=0; EntityNr<MFEntityList.Size(); EntityNr++) |
|---|
| 397 | { |
|---|
| 398 | const MapFileEntityT& E=MFEntityList[EntityNr]; |
|---|
| 399 | const std::map<std::string, std::string>::const_iterator ClassNamePair=E.MFProperties.find("classname"); |
|---|
| 400 | |
|---|
| 401 | if (ClassNamePair==E.MFProperties.end()) Error("\"classname\" property not found in entity %lu.", EntityNr); |
|---|
| 402 | |
|---|
| 403 | if (ClassNamePair->second=="worldspawn") |
|---|
| 404 | { |
|---|
| 405 | std::map<std::string, std::string>::const_iterator It; |
|---|
| 406 | |
|---|
| 407 | It=E.MFProperties.find("lightmap_patchsize"); |
|---|
| 408 | if (It!=E.MFProperties.end()) |
|---|
| 409 | { |
|---|
| 410 | cf::SceneGraph::FaceNodeT::LightMapInfoT::PatchSize=float(atof(It->second.c_str())); |
|---|
| 411 | |
|---|
| 412 | if (cf::SceneGraph::FaceNodeT::LightMapInfoT::PatchSize< 50.0) { cf::SceneGraph::FaceNodeT::LightMapInfoT::PatchSize= 50.0; Console->Print("NOTE: LightMap PatchSize clamped to 50.\n" ); } |
|---|
| 413 | if (cf::SceneGraph::FaceNodeT::LightMapInfoT::PatchSize>2000.0) { cf::SceneGraph::FaceNodeT::LightMapInfoT::PatchSize=2000.0; Console->Print("NOTE: LightMap PatchSize clamped to 2000.\n"); } |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | It=E.MFProperties.find("shlmap_patchsize"); |
|---|
| 417 | if (It!=E.MFProperties.end()) |
|---|
| 418 | { |
|---|
| 419 | cf::SceneGraph::FaceNodeT::SHLMapInfoT::PatchSize=float(atof(It->second.c_str())); |
|---|
| 420 | |
|---|
| 421 | if (cf::SceneGraph::FaceNodeT::SHLMapInfoT::PatchSize< 50.0) { cf::SceneGraph::FaceNodeT::SHLMapInfoT::PatchSize= 50.0; Console->Print("NOTE: SHLMap PatchSize clamped to 50.\n" ); } |
|---|
| 422 | if (cf::SceneGraph::FaceNodeT::SHLMapInfoT::PatchSize>2000.0) { cf::SceneGraph::FaceNodeT::SHLMapInfoT::PatchSize=2000.0; Console->Print("NOTE: SHLMap PatchSize clamped to 2000.\n"); } |
|---|
| 423 | } |
|---|
| 424 | |
|---|
| 425 | |
|---|
| 426 | for (unsigned long BrushNr=0; BrushNr<E.MFBrushes.Size(); BrushNr++) |
|---|
| 427 | ComputeBrushFaces(E.MFBrushes[BrushNr], World, World.BspTree, DrawWorldOutsidePointSamples, EntityNr, BrushNr); |
|---|
| 428 | |
|---|
| 429 | for (unsigned long BPNr=0; BPNr<E.MFPatches.Size(); BPNr++) |
|---|
| 430 | { |
|---|
| 431 | const MapFileBezierPatchT& BP=E.MFPatches[BPNr]; |
|---|
| 432 | |
|---|
| 433 | World.BspTree->OtherChildren.PushBack(new cf::SceneGraph::BezierPatchNodeT(World.LightMapMan, BP.SizeX, BP.SizeY, BP.ControlPoints, BP.SubdivsHorz, BP.SubdivsVert, BP.Material)); |
|---|
| 434 | } |
|---|
| 435 | |
|---|
| 436 | for (unsigned long TerrainNr=0; TerrainNr<E.MFTerrains.Size(); TerrainNr++) |
|---|
| 437 | { |
|---|
| 438 | const MapFileTerrainT& Terrain=E.MFTerrains[TerrainNr]; |
|---|
| 439 | |
|---|
| 440 | World.Terrains.PushBack(new SharedTerrainT(Terrain.Bounds, Terrain.SideLength, Terrain.HeightData, Terrain.Material)); |
|---|
| 441 | World.BspTree->OtherChildren.PushBack(new cf::SceneGraph::TerrainNodeT(Terrain.Bounds, World.Terrains[TerrainNr]->Terrain, TerrainNr, Terrain.Material->Name)); |
|---|
| 442 | } |
|---|
| 443 | |
|---|
| 444 | for (unsigned long PlantNr=0; PlantNr<E.MFPlants.Size(); PlantNr++) |
|---|
| 445 | { |
|---|
| 446 | const MapFilePlantT& Plant=E.MFPlants[PlantNr]; |
|---|
| 447 | |
|---|
| 448 | World.BspTree->OtherChildren.PushBack(new cf::SceneGraph::PlantNodeT(World.PlantDescrMan.GetPlantDescription(Plant.DescrFileName) , Plant.RandomSeed, Plant.Position, Plant.Angles)); |
|---|
| 449 | } |
|---|
| 450 | |
|---|
| 451 | for (unsigned long ModelNr=0; ModelNr<E.MFModels.Size(); ModelNr++) |
|---|
| 452 | { |
|---|
| 453 | std::string ErrorMsg; |
|---|
| 454 | const MapFileModelT& Model=E.MFModels[ModelNr]; |
|---|
| 455 | const CafuModelT* CafuM=ModelMan.GetModel(GameDirectory+"/"+Model.Model, &ErrorMsg); |
|---|
| 456 | |
|---|
| 457 | if (ErrorMsg!="") Console->Warning(ErrorMsg); |
|---|
| 458 | World.BspTree->OtherChildren.PushBack(new cf::SceneGraph::ModelNodeT(CafuM, Model.Label, Model.Origin, Model.Angles, Model.Scale, Model.SeqNumber, Model.FrameOffset, Model.FrameTimeScale, Model.Animate)); |
|---|
| 459 | } |
|---|
| 460 | } |
|---|
| 461 | else if (ClassNamePair->second=="PointLight") |
|---|
| 462 | { |
|---|
| 463 | const double Pi=3.14159265358979323846; |
|---|
| 464 | |
|---|
| 465 | PointLightT PL; |
|---|
| 466 | std::map<std::string, std::string>::const_iterator It; |
|---|
| 467 | |
|---|
| 468 | PL.Dir =VectorT(0.0, 1.0, 0.0); |
|---|
| 469 | PL.Angle=float(Pi); |
|---|
| 470 | |
|---|
| 471 | It=E.MFProperties.find("origin" ); if (It!=E.MFProperties.end()) PL.Origin =GetVectorFromTripleToken(It->second)*CA3DE_SCALE; |
|---|
| 472 | It=E.MFProperties.find("angles" ); if (It!=E.MFProperties.end()) PL.Dir =Vector3dT(0, 0, -1); // Sigh... yet another hack! :-/ // GetVectorFromTripleToken(It->second); |
|---|
| 473 | It=E.MFProperties.find("opening_angle"); if (It!=E.MFProperties.end()) PL.Angle =float(atof(It->second.c_str())/180.0*Pi); |
|---|
| 474 | It=E.MFProperties.find("intensity_r" ); if (It!=E.MFProperties.end()) PL.Intensity.x=atof(It->second.c_str()); |
|---|
| 475 | It=E.MFProperties.find("intensity_g" ); if (It!=E.MFProperties.end()) PL.Intensity.y=atof(It->second.c_str()); |
|---|
| 476 | It=E.MFProperties.find("intensity_b" ); if (It!=E.MFProperties.end()) PL.Intensity.z=atof(It->second.c_str()); |
|---|
| 477 | |
|---|
| 478 | if (PL.Angle<0.0) PL.Angle=0.0; |
|---|
| 479 | if (PL.Angle> Pi) PL.Angle=float(Pi); |
|---|
| 480 | |
|---|
| 481 | World.PointLights.PushBack(PL); |
|---|
| 482 | } |
|---|
| 483 | /* else if (ClassNamePair->second=="func_wall") // Special case, handled above. |
|---|
| 484 | { |
|---|
| 485 | } |
|---|
| 486 | else if (ClassNamePair->second=="func_water") // Special case, handled above. |
|---|
| 487 | { |
|---|
| 488 | } */ |
|---|
| 489 | else if (ClassNamePair->second=="info_player_start") |
|---|
| 490 | { |
|---|
| 491 | InfoPlayerStartT IPS; |
|---|
| 492 | std::map<std::string, std::string>::const_iterator It; |
|---|
| 493 | |
|---|
| 494 | IPS.Heading=0; |
|---|
| 495 | IPS.Pitch =0; |
|---|
| 496 | IPS.Bank =0; |
|---|
| 497 | |
|---|
| 498 | It=E.MFProperties.find("origin"); if (It!=E.MFProperties.end()) IPS.Origin =GetVectorFromTripleToken(It->second.c_str())*CA3DE_SCALE; |
|---|
| 499 | It=E.MFProperties.find("angles"); if (It!=E.MFProperties.end()) IPS.Heading=(unsigned short)(GetVectorFromTripleToken(It->second).y*8192.0/45.0); |
|---|
| 500 | |
|---|
| 501 | World.InfoPlayerStarts.PushBack(IPS); |
|---|
| 502 | } |
|---|
| 503 | else |
|---|
| 504 | { |
|---|
| 505 | // Console->Print("INFO: PROCESSING GAME ENTITY "+cf::va("%lu", World.GameEntities.Size())+" (class \""+ClassNamePair->second+"\"):\n"); |
|---|
| 506 | // Console->Print("***********************************************************************************\n\n"); |
|---|
| 507 | |
|---|
| 508 | // Es ist ein Game/MOD/DLL-Entity, kein Engine-Entity! |
|---|
| 509 | GameEntityT* GE=new GameEntityT(E.MFIndex); |
|---|
| 510 | |
|---|
| 511 | // 1. Copy the properties. |
|---|
| 512 | GE->Properties=E.MFProperties; |
|---|
| 513 | |
|---|
| 514 | // 2. Copy the origin point. |
|---|
| 515 | std::map<std::string, std::string>::const_iterator It=E.MFProperties.find("origin"); |
|---|
| 516 | if (It!=E.MFProperties.end()) |
|---|
| 517 | { |
|---|
| 518 | // Der Origin ist etwas besonderes, denn er kommt bei allen "point entities" vor |
|---|
| 519 | // (bei "solid entities" idR in Form eines Origin-Brushes), |
|---|
| 520 | // und in der Engine (EntityStateT) sogar bei allen Entities. |
|---|
| 521 | GE->Origin=GetVectorFromTripleToken(It->second)*CA3DE_SCALE; |
|---|
| 522 | } |
|---|
| 523 | |
|---|
| 524 | // 3. Fill-in the Terrains array. |
|---|
| 525 | for (unsigned long TerrainNr=0; TerrainNr<E.MFTerrains.Size(); TerrainNr++) |
|---|
| 526 | { |
|---|
| 527 | const MapFileTerrainT& Terrain=E.MFTerrains[TerrainNr]; |
|---|
| 528 | |
|---|
| 529 | GE->Terrains.PushBack(new SharedTerrainT(Terrain.Bounds, Terrain.SideLength, Terrain.HeightData, Terrain.Material)); |
|---|
| 530 | } |
|---|
| 531 | |
|---|
| 532 | // 4. Create a new BSP tree. |
|---|
| 533 | GE->BspTree=new cf::SceneGraph::BspTreeNodeT; |
|---|
| 534 | |
|---|
| 535 | // 5. Build the collision model (if this entity has one that is made of map primitives). |
|---|
| 536 | if (E.MFBrushes.Size()>0 || E.MFPatches.Size()>0 || GE->Terrains.Size()>0) |
|---|
| 537 | { |
|---|
| 538 | ArrayT<cf::ClipSys::CollisionModelStaticT::TerrainRefT> ShTe; |
|---|
| 539 | |
|---|
| 540 | for (unsigned long TerrainNr=0; TerrainNr<GE->Terrains.Size(); TerrainNr++) |
|---|
| 541 | { |
|---|
| 542 | const SharedTerrainT* ST=GE->Terrains[TerrainNr]; |
|---|
| 543 | |
|---|
| 544 | ShTe.PushBack(cf::ClipSys::CollisionModelStaticT::TerrainRefT(&ST->Terrain, ST->Material, ST->BB)); |
|---|
| 545 | } |
|---|
| 546 | |
|---|
| 547 | GE->CollModel=new cf::ClipSys::CollisionModelStaticT(E, ShTe, true /*Use generic brushes.*/); |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | // 6. Collect the geometry primitives for the BSP tree. |
|---|
| 551 | ArrayT<VectorT> GameEntityOutsidePointSamples; |
|---|
| 552 | |
|---|
| 553 | for (unsigned long BrushNr=0; BrushNr<E.MFBrushes.Size(); BrushNr++) |
|---|
| 554 | { |
|---|
| 555 | ComputeBrushFaces(E.MFBrushes[BrushNr], World, GE->BspTree, GameEntityOutsidePointSamples, EntityNr, BrushNr); |
|---|
| 556 | } |
|---|
| 557 | |
|---|
| 558 | for (unsigned long BPNr=0; BPNr<E.MFPatches.Size(); BPNr++) |
|---|
| 559 | { |
|---|
| 560 | const MapFileBezierPatchT& BP=E.MFPatches[BPNr]; |
|---|
| 561 | |
|---|
| 562 | GE->BspTree->OtherChildren.PushBack(new cf::SceneGraph::BezierPatchNodeT(World.LightMapMan, BP.SizeX, BP.SizeY, BP.ControlPoints, BP.SubdivsHorz, BP.SubdivsVert, BP.Material)); |
|---|
| 563 | } |
|---|
| 564 | |
|---|
| 565 | for (unsigned long TerrainNr=0; TerrainNr<E.MFTerrains.Size(); TerrainNr++) |
|---|
| 566 | { |
|---|
| 567 | const MapFileTerrainT& Terrain=E.MFTerrains[TerrainNr]; |
|---|
| 568 | |
|---|
| 569 | // This has already done been done above: GE->Terrains.PushBack(new SharedTerrainT(...)); |
|---|
| 570 | GE->BspTree->OtherChildren.PushBack(new cf::SceneGraph::TerrainNodeT(Terrain.Bounds, GE->Terrains[TerrainNr]->Terrain, TerrainNr, Terrain.Material->Name)); |
|---|
| 571 | } |
|---|
| 572 | |
|---|
| 573 | for (unsigned long PlantNr=0; PlantNr<E.MFPlants.Size(); PlantNr++) |
|---|
| 574 | { |
|---|
| 575 | const MapFilePlantT& Plant=E.MFPlants[PlantNr]; |
|---|
| 576 | |
|---|
| 577 | GE->BspTree->OtherChildren.PushBack(new cf::SceneGraph::PlantNodeT(World.PlantDescrMan.GetPlantDescription(Plant.DescrFileName) , Plant.RandomSeed, Plant.Position, Plant.Angles)); |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | for (unsigned long ModelNr=0; ModelNr<E.MFModels.Size(); ModelNr++) |
|---|
| 581 | { |
|---|
| 582 | std::string ErrorMsg; |
|---|
| 583 | const MapFileModelT& Model=E.MFModels[ModelNr]; |
|---|
| 584 | const CafuModelT* CafuM=ModelMan.GetModel(GameDirectory+"/"+Model.Model, &ErrorMsg); |
|---|
| 585 | |
|---|
| 586 | if (ErrorMsg!="") Console->Warning(ErrorMsg); |
|---|
| 587 | GE->BspTree->OtherChildren.PushBack(new cf::SceneGraph::ModelNodeT(CafuM, Model.Label, Model.Origin, Model.Angles, Model.Scale, Model.SeqNumber, Model.FrameOffset, Model.FrameTimeScale, Model.Animate)); |
|---|
| 588 | } |
|---|
| 589 | |
|---|
| 590 | |
|---|
| 591 | // 7. Compute the BSP tree. |
|---|
| 592 | // It's done immediately here rather than after the map has been loaded completely |
|---|
| 593 | // so that we don't have to keep the OutsidePointSamples array. |
|---|
| 594 | BspTreeBuilderT BspTreeBuilder(GE->BspTree, false /*most simple tree*/, false /*min face splits*/); |
|---|
| 595 | |
|---|
| 596 | ArrayT<Vector3dT> EmptyFloodFillSources; |
|---|
| 597 | std::string EmptyMapFileName=""; // Entity BSP trees aren't flood-filled, so they cannot leak, so we never need to write a .pts point file for them. |
|---|
| 598 | |
|---|
| 599 | // Temporarily filter the console output by redirecting everything through the warnings-only console. |
|---|
| 600 | cf::ConsoleWarningsOnlyT ConWarnOnly(Console); |
|---|
| 601 | cf::ConsoleI* PrevConsole=Console; |
|---|
| 602 | Console=&ConWarnOnly; |
|---|
| 603 | |
|---|
| 604 | BspTreeBuilder.Build(ClassNamePair->second=="worldspawn", EmptyFloodFillSources, GameEntityOutsidePointSamples, EmptyMapFileName); |
|---|
| 605 | |
|---|
| 606 | // Restore the previous console. |
|---|
| 607 | Console=PrevConsole; |
|---|
| 608 | |
|---|
| 609 | // 8. Add the entity to the worlds list. |
|---|
| 610 | World.GameEntities.PushBack(GE); |
|---|
| 611 | } |
|---|
| 612 | } |
|---|
| 613 | |
|---|
| 614 | |
|---|
| 615 | Console->Print("All game entities done, processing the worldspawn entity now.\n"); |
|---|
| 616 | Console->Print(std::string("Preparing worldspawn clip data (")+GetTimeSinceProgramStart()+")..."); |
|---|
| 617 | |
|---|
| 618 | ArrayT<cf::ClipSys::CollisionModelStaticT::TerrainRefT> ShTe; |
|---|
| 619 | |
|---|
| 620 | for (unsigned long TerrainNr=0; TerrainNr<World.Terrains.Size(); TerrainNr++) |
|---|
| 621 | { |
|---|
| 622 | const SharedTerrainT* ST=World.Terrains[TerrainNr]; |
|---|
| 623 | |
|---|
| 624 | ShTe.PushBack(cf::ClipSys::CollisionModelStaticT::TerrainRefT(&ST->Terrain, ST->Material, ST->BB)); |
|---|
| 625 | } |
|---|
| 626 | |
|---|
| 627 | World.CollModel=new cf::ClipSys::CollisionModelStaticT(MFEntityList[0], ShTe, false /*Use brushes with precomputed bevel planes.*/); |
|---|
| 628 | Console->Print(std::string(" done (")+GetTimeSinceProgramStart()+").\n"); |
|---|
| 629 | |
|---|
| 630 | Console->Print(cf::va("Face Children : %10lu Draw World Outer Point Samples: %5lu\n", World.BspTree->FaceChildren.Size(), DrawWorldOutsidePointSamples.Size())); |
|---|
| 631 | Console->Print(cf::va("PointLights : %10lu\n", World.PointLights.Size())); |
|---|
| 632 | Console->Print(cf::va("InfoPlayerStarts : %10lu\n", World.InfoPlayerStarts.Size())); |
|---|
| 633 | Console->Print(cf::va("Other Children : %10lu\n", World.BspTree->OtherChildren.Size())); |
|---|
| 634 | Console->Print(cf::va("GameEntities : %10lu\n", World.GameEntities.Size())); |
|---|
| 635 | } |
|---|