2 Cafu Engine, http://www.cafu.de/
3 Copyright (c) Carsten Fuchs and other contributors.
4 This project is licensed under the terms of the MIT license.
7 #include "Ca3DEWorld.hpp"
8 #include "EngineEntity.hpp"
9 #include "ClipSys/ClipWorld.hpp"
10 #include "ClipSys/CollisionModel_static.hpp"
11 #include "ClipSys/CollisionModelMan.hpp"
12 #include "ClipSys/TraceResult.hpp"
13 #include "ClipSys/TraceSolid.hpp"
14 #include "ConsoleCommands/Console.hpp" // For cf::va().
15 #include "GameSys/World.hpp"
16 #include "GuiSys/AllComponents.hpp" // for initing the m_ScriptState
17 #include "GuiSys/GuiImpl.hpp" // for initing the m_ScriptState
18 #include "GuiSys/Window.hpp" // for initing the m_ScriptState
19 #include "MaterialSystem/Material.hpp"
20 #include "Models/ModelManager.hpp"
21 #include "../Common/CompGameEntity.hpp"
22 #include "../Common/WorldMan.hpp"
23 #include "SceneGraph/BspTreeNode.hpp"
27 #if defined(_WIN32) && defined(_MSC_VER)
28 // Turn off warning 4355: "'this' : wird in Initialisierungslisten fuer Basisklasse verwendet".
29 #pragma warning(disable:4355)
33 static WorldManT WorldMan
;
36 Ca3DEWorldT::Ca3DEWorldT(const char* FileName
, ModelManagerT
& ModelMan
, cf::GuiSys::GuiResourcesT
& GuiRes
, bool InitForGraphics
, WorldT::ProgressFunctionT ProgressFunction
) /*throw (WorldT::LoadErrorT)*/
37 : m_World(WorldMan
.LoadWorld(FileName
, ModelMan
, GuiRes
, InitForGraphics
, ProgressFunction
)),
38 m_ClipWorld(new cf::ClipSys::ClipWorldT(m_World
->m_StaticEntityData
[0]->m_CollModel
)),
39 m_PhysicsWorld(m_World
->m_StaticEntityData
[0]->m_CollModel
),
40 m_ScriptState(new cf::UniScriptStateT
), // Need a pointer because the dtor order is important.
44 cf::GameSys::WorldT::InitScriptState(*m_ScriptState
);
47 // We cannot use this method, which in fact is kind of obsolete:
48 // It would attempt to re-register the Console and ConsoleInterface libraries,
49 // which was already done above in cf::GameSys::WorldT::InitScriptState().
50 // (Both InitScriptState() methods should probably be removed / refactored.)
51 cf::GuiSys::GuiImplT::InitScriptState(*m_ScriptState
);
54 // For each class that the TypeInfoManTs know about, add a (meta-)table to the registry of the LuaState.
55 // The (meta-)table holds the Lua methods that the respective class implements in C++,
56 // and is to be used as metatable for instances of this class.
57 cf::ScriptBinderT
Binder(m_ScriptState
->GetLuaState());
59 Binder
.Init(cf::GuiSys::GetGuiTIM());
60 Binder
.Init(cf::GuiSys::GetWindowTIM());
61 Binder
.Init(cf::GuiSys::GetComponentTIM());
67 std::string ScriptName
= cf::String::StripExt(FileName
) + ".cent";
68 ScriptName
= cf::String::Replace(ScriptName
, "/Worlds/", "/Maps/");
69 ScriptName
= cf::String::Replace(ScriptName
, "\\Worlds\\", "\\Maps\\");
71 m_ScriptWorld
= new cf::GameSys::WorldT(
72 InitForGraphics
? cf::GameSys::WorldT::RealmClient
: cf::GameSys::WorldT::RealmServer
,
76 *cf::ClipSys::CollModelMan
, // TODO: The CollModelMan should not be a global, but rather be instantiated along with the ModelMan and GuiRes.
80 m_ScriptWorld
->LoadScript(ScriptName
, 0 /*cf::GameSys::WorldT::InitFlag_OnlyStatic*/);
82 catch (const cf::GameSys::WorldT::InitErrorT
& IE
)
84 throw WorldT::LoadErrorT(IE
.what());
88 ArrayT
< IntrusivePtrT
<cf::GameSys::EntityT
> > AllEnts
;
89 m_ScriptWorld
->GetRootEntity()->GetAll(AllEnts
);
91 // For player prototype entities make sure that their models are not shown.
92 // Showing their models may be desirable in the Map Editor, but not in the Engine.
93 for (unsigned int EntNr
= 0; EntNr
< AllEnts
.Size(); EntNr
++)
95 if (AllEnts
[EntNr
]->GetComponent("HumanPlayer") != NULL
)
97 for (unsigned int i
= 0; true; i
++)
99 IntrusivePtrT
<cf::GameSys::ComponentBaseT
> ModelComp
= AllEnts
[EntNr
]->GetComponent("Model", i
);
101 if (ModelComp
== NULL
) break;
102 ModelComp
->SetMember("Show", false);
108 // - CompGameEntityT instance and
109 // - EngineEntityT instance
110 // for each entity in the script world (including the world (root) entity).
111 if (AllEnts
.Size() != m_World
->m_StaticEntityData
.Size())
112 throw WorldT::LoadErrorT("The number of entities in the .cent file must match the number of entities in the .bsp file.");
114 for (unsigned int EntNr
= 0; EntNr
< AllEnts
.Size(); EntNr
++)
116 // This is also checked in the `cf::GameSys::WorldT` ctor, see there for details.
117 // It is repeated here as a reminder: entity IDs are used as indices into m_World->m_StaticEntityData[].
118 assert(AllEnts
[EntNr
]->GetID() == EntNr
);
120 AllEnts
[EntNr
]->SetApp(new CompGameEntityT(m_World
->m_StaticEntityData
[EntNr
]));
121 CreateNewEntityFromBasicInfo(AllEnts
[EntNr
], 1 /*ServerFrameNr*/);
124 // Have all components of all entities precache their resources.
125 for (unsigned int EntNr
= 0; EntNr
< AllEnts
.Size(); EntNr
++)
127 const ArrayT
< IntrusivePtrT
<cf::GameSys::ComponentBaseT
> >& Components
= AllEnts
[EntNr
]->GetComponents();
129 for (unsigned int CompNr
= 0; CompNr
< Components
.Size(); CompNr
++)
131 Components
[CompNr
]->PreCache();
137 Ca3DEWorldT::~Ca3DEWorldT()
139 // Note that the engine entities must be destructed *before* the ClipWorld,
140 // so that they properly remove their clip model from the clip world on destruction.
141 for (unsigned long EntityNr
=0; EntityNr
<m_EngineEntities
.Size(); EntityNr
++)
142 delete m_EngineEntities
[EntityNr
];
144 m_EngineEntities
.Clear();
146 m_ScriptWorld
= NULL
;
148 // The script state may still hold entities that have collision model components that have registered
149 // collision models with the clip world. Thus, make sure to delete the script state before the clip world,
150 // so that the clip world is left clean.
151 delete m_ScriptState
;
152 m_ScriptState
= NULL
;
154 // delete m_PhysicsWorld;
155 // m_PhysicsWorld = NULL;
162 WorldMan
.FreeWorld(m_World
);
168 Vector3fT
Ca3DEWorldT::GetAmbientLightColorFromBB(const BoundingBox3T
<double>& Dimensions
, const VectorT
& Origin
) const
170 const Vector3dT BBCenter
= scale(Dimensions
.Min
+Dimensions
.Max
, 0.5) + Origin
;
171 const cf::SceneGraph::BspTreeNodeT
* BspTree
= m_World
->m_StaticEntityData
[0]->m_BspTree
;
174 // Performance profiling revealed that this method is frequently called
175 // and that the call to CollModel->TraceRay() is pretty expensive!
176 const Vector3dT Ray
=Vector3dT(0.0, 0.0, -999999.0);
178 cf::ClipSys::TraceResultT
Result(1.0);
179 CollModel
->TraceRay(BBCenter
, Ray
, MaterialT::Clip_Radiance
, Result
);
181 Vector3dT Ground
=BBCenter
+Ray
*Result
.Fraction
+Vector3dT(0, 0, 0.2);
183 // We therefore revert to this call, which is similarly limited (considers faces only) but is *much* faster!
184 // Note that the proper future solution is to reimplement this method to use a precomputed lighting grid!!
185 const double Trace
=BspTree
->ClipLine(BBCenter
, Vector3dT(0.0, 0.0, -1.0), 0.0, 999999.0);
186 Vector3dT Ground
=BBCenter
+Vector3dT(0.0, 0.0, -(Trace
-0.2));
188 unsigned long LeafNr
=BspTree
->WhatLeaf(Ground
);
190 // This is a relatively cheap (dumb) trick for dealing with problematic input (like some static detail models).
191 if (!BspTree
->Leaves
[LeafNr
].IsInnerLeaf
)
193 const VectorT TestPoints
[4]={ VectorT(Dimensions
.Min
.x
, Dimensions
.Min
.y
, Dimensions
.Max
.z
)+Origin
,
194 VectorT(Dimensions
.Min
.x
, Dimensions
.Max
.y
, Dimensions
.Max
.z
)+Origin
,
195 VectorT(Dimensions
.Max
.x
, Dimensions
.Min
.y
, Dimensions
.Max
.z
)+Origin
,
196 VectorT(Dimensions
.Max
.x
, Dimensions
.Max
.y
, Dimensions
.Max
.z
)+Origin
};
200 for (unsigned long TestNr
=0; TestNr
<4; TestNr
++)
203 // Same performance issue as above...
204 CollModel
->TraceRay(TestPoints
[TestNr
], Ray
, MaterialT::Clip_Radiance
, Result
); // BUG / FIXME: I think we have to re-init Result here!
205 const Vector3dT TestGround
=TestPoints
[TestNr
]+Ray
*Result
.Fraction
+Vector3dT(0, 0, 0.2);
207 const double TestTrace
=BspTree
->ClipLine(TestPoints
[TestNr
], Vector3dT(0.0, 0.0, -1.0), 0.0, 999999.0);
208 const Vector3dT TestGround
=TestPoints
[TestNr
]+Vector3dT(0.0, 0.0, -(TestTrace
-0.2));
210 const unsigned long TestLeafNr
=BspTree
->WhatLeaf(TestGround
);
212 if (!BspTree
->Leaves
[TestLeafNr
].IsInnerLeaf
) continue;
214 if (TestGround
.z
>Ground
.z
)
222 for (unsigned long FNr
=0; FNr
<BspTree
->Leaves
[LeafNr
].FaceChildrenSet
.Size(); FNr
++)
224 cf::SceneGraph::FaceNodeT
* FaceNode
=BspTree
->FaceChildren
[BspTree
->Leaves
[LeafNr
].FaceChildrenSet
[FNr
]];
225 Vector3fT AmbientLightColor
;
227 if (FaceNode
->GetLightmapColorNearPosition(Ground
, AmbientLightColor
, BspTree
->GetLightMapPatchSize()))
228 return AmbientLightColor
;
231 return Vector3fT(1.0f
, 1.0f
, 1.0f
);
235 void Ca3DEWorldT::CreateNewEntityFromBasicInfo(IntrusivePtrT
<cf::GameSys::EntityT
> Ent
, unsigned long CreationFrameNr
)
237 const unsigned long NewEntityID
= Ent
->GetID();
239 while (m_EngineEntities
.Size() <= NewEntityID
)
240 m_EngineEntities
.PushBack(NULL
);
242 // Well... quite clearly, EngineEntityT should be merged into the App component...!
243 // However, note that there is one very important requirement:
244 // Iterating over the m_EngineEntities array iterates the entities in the order of increasing ID.
245 // The CaServerWorldT::WriteClientDeltaUpdateMessages() and the related client code *rely* on this order!
246 assert(m_EngineEntities
[NewEntityID
] == NULL
);
247 delete m_EngineEntities
[NewEntityID
];
248 m_EngineEntities
[NewEntityID
] = new EngineEntityT(Ent
, CreationFrameNr
);