group2 0.1.0
CSE 125 Group 2
Loading...
Searching...
No Matches
MapConfig.hpp
Go to the documentation of this file.
1
20
21#pragma once
22
23#include "ecs/AssetCatalog.hpp"
26
27#include <SDL3/SDL_filesystem.h>
28#include <SDL3/SDL_log.h>
29
30#include <assimp/Importer.hpp>
31#include <assimp/postprocess.h>
32#include <assimp/scene.h>
33#include <fstream>
34#include <functional>
35#include <iostream>
36#include <string>
37
38namespace gamemap
39{
40
53inline constexpr bool k_separatedCollisionMap = false;
54
57inline constexpr const char* k_collisionPattern = "COL_";
58
81inline constexpr bool k_useVhacd = false;
82
89{
91 opts.scale = kMapAsset.loadScale;
95 opts.addFloorPlane = false; // Map geometry provides its own floor.
96 return opts;
97}
98
103[[nodiscard]] inline std::string mapAbsolutePath()
104{
105 const char* const base = SDL_GetBasePath();
106 return std::string(base ? base : "") + "assets/" + kMapAsset.filename;
107}
108
111{
112 glm::vec3 pos{0.0f};
113 float yaw = 0.0f;
114};
115inline std::vector<SpawnPoint> spawnPoints_;
116
118{
120 glm::vec3 pos;
121};
122inline std::vector<WeaponSpawner> weaponSpawner_;
123
125{
127 glm::vec3 pos;
128};
129inline std::vector<PowerupSpawner> powerupSpawner_;
130
132{
133 glm::vec3 pos;
134};
135inline std::vector<HealthPackSpawner> healthPackSpawner_;
136
143{
144 glm::vec3 pos{0.0f};
145 glm::vec3 velocity{0.0f, 1500.0f, 0.0f};
146 glm::vec3 halfExtents{48.0f, 24.0f, 48.0f};
147};
148inline std::vector<JumpPadSpawner> jumpPadSpawner_;
149
154{
155 glm::vec3 pos{0.0f};
156 glm::vec3 halfExtents{674.0f, 184.0f, 499.0f};
157};
158inline std::vector<KillzoneSpawner> killzoneSpawner_;
159
160void traverseNodeTree(const aiNode* node, int depth = 0)
161{
162 if (node == nullptr) {
163 return;
164 }
165
166 for (int i = 0; i < depth; i++) {
167 std::cout << " ";
168 }
169
170 std::cout << node->mName.C_Str() << "\n";
171
172 for (unsigned int i = 0; i < node->mNumChildren; i++) {
173 traverseNodeTree(node->mChildren[i], depth + 1);
174 }
175}
176
177void* getMetadataValue(const aiMetadata* metadata, const std::string& key)
178{
179 if (metadata == nullptr) {
180 return nullptr;
181 }
182
183 for (unsigned int i = 0; i < metadata->mNumProperties; i++) {
184 if (key == metadata->mKeys[i].C_Str()) {
185 return metadata->mValues[i].mData;
186 }
187 }
188
189 return nullptr;
190}
191
195inline float getMetadataFloat(const aiMetadata* metadata, const std::string& key, float fallback)
196{
197 if (metadata == nullptr)
198 return fallback;
199 for (unsigned int i = 0; i < metadata->mNumProperties; i++) {
200 if (key != metadata->mKeys[i].C_Str())
201 continue;
202 const aiMetadataEntry& entry = metadata->mValues[i];
203 switch (entry.mType) {
204 case AI_FLOAT:
205 return *static_cast<float*>(entry.mData);
206 case AI_DOUBLE:
207 return static_cast<float>(*static_cast<double*>(entry.mData));
208 case AI_INT32:
209 return static_cast<float>(*static_cast<int32_t*>(entry.mData));
210 default:
211 return fallback;
212 }
213 }
214 return fallback;
215}
216
226inline bool loadConfiguredMap(physics::MapCollisionData& out, const char* tag)
227{
228 const std::string path = mapAbsolutePath();
230
231 if (!physics::loadMapCollision(path, out, opts)) {
232 SDL_Log("[%s] WARNING: map collision load failed (%s) — falling back to testWorld()", tag, kMapAsset.filename);
233 return false;
234 }
235
236 SDL_Log("[%s] map collision loaded (%s): %zu planes, %zu boxes, %zu brushes, %zu cylinders, %zu spheres, "
237 "%zu trimeshes",
238 tag,
239 kMapAsset.filename,
240 out.planes.size(),
241 out.boxes.size(),
242 out.brushes.size(),
243 out.cylinders.size(),
244 out.spheres.size(),
245 out.triMeshes.size());
246
247 // Load gameplay entities from map
248 Assimp::Importer importer;
249 const aiScene* scene = importer.ReadFile(path, 0 /* no flags */);
250
251 if (scene == nullptr)
252 return false;
253
254 std::function<void(const aiNode*, int)> traverse = [&](const aiNode* node, int depth) {
255 if (node == nullptr) {
256 return;
257 }
258
259 if (node->mMetaData) {
260 void* entity_type_ptr = getMetadataValue(node->mMetaData, "entity_type");
261 if (entity_type_ptr != nullptr) {
262 const int32_t entity_type = *static_cast<int32_t*>(entity_type_ptr);
263 const char* const nodeName = node->mName.C_Str();
264 switch (entity_type) {
265 case 0: // Player spawn point
266 {
267 const aiMatrix4x4& t = node->mTransformation;
268 glm::vec3 pos = glm::vec3(t.a4, t.b4, t.c4) * kMapAsset.loadScale;
269 // Facing yaw from the node's local -Z axis (glTF/OpenGL
270 // forward), projected onto the XZ plane. Matches
271 // viewForward(): yaw = atan2(fwd.x, fwd.z). Falls back to 0
272 // (facing +Z) when the spawn wasn't rotated in the map.
273 const glm::vec3 fwd{-t.a3, -t.b3, -t.c3};
274 float yaw = 0.0f;
275 if (std::abs(fwd.x) > 1e-4f || std::abs(fwd.z) > 1e-4f) {
276 yaw = std::atan2(fwd.x, fwd.z);
277 }
278 spawnPoints_.push_back(SpawnPoint{.pos = pos, .yaw = yaw});
279 break;
280 }
281 case 1: // Weapon spawn point
282 {
283 void* weapon_type_ptr = getMetadataValue(node->mMetaData, "weapon_type");
284 if (weapon_type_ptr == nullptr) {
285 SDL_Log("Weapon spawner '%s' missing 'weapon_type' metadata — skipping", nodeName);
286 break;
287 }
288 const aiMatrix4x4& t = node->mTransformation;
289 WeaponType weapon_type = static_cast<WeaponType>(*static_cast<int32_t*>(weapon_type_ptr));
290 glm::vec3 pos = glm::vec3(t.a4, t.b4, t.c4) * kMapAsset.loadScale;
291 weaponSpawner_.push_back(WeaponSpawner{.type = weapon_type, .pos = pos});
292 break;
293 }
294 case 2: // Power up spawn point
295 {
296 void* powerup_type_ptr = getMetadataValue(node->mMetaData, "powerup_type");
297 if (powerup_type_ptr == nullptr) {
298 SDL_Log("Powerup spawner '%s' missing 'powerup_type' metadata — skipping", nodeName);
299 break;
300 }
301 const aiMatrix4x4& t = node->mTransformation;
302 PowerupType powerup_type = static_cast<PowerupType>(*static_cast<int32_t*>(powerup_type_ptr));
303 glm::vec3 pos = glm::vec3(t.a4, t.b4, t.c4) * kMapAsset.loadScale;
304 powerupSpawner_.push_back(PowerupSpawner{.type = powerup_type, .pos = pos});
305 break;
306 }
307 case 3: // Jump pad
308 {
309 const aiMatrix4x4& t = node->mTransformation;
310 glm::vec3 pos = glm::vec3(t.a4, t.b4, t.c4) * kMapAsset.loadScale;
311 JumpPadSpawner pad;
312 pad.pos = pos;
313 pad.velocity.x = getMetadataFloat(node->mMetaData, "jump_velocity_x", pad.velocity.x);
314 pad.velocity.y = getMetadataFloat(node->mMetaData, "jump_velocity_y", pad.velocity.y);
315 pad.velocity.z = getMetadataFloat(node->mMetaData, "jump_velocity_z", pad.velocity.z);
316 pad.halfExtents.x = getMetadataFloat(node->mMetaData, "half_extent_x", pad.halfExtents.x);
317 pad.halfExtents.y = getMetadataFloat(node->mMetaData, "half_extent_y", pad.halfExtents.y);
318 pad.halfExtents.z = getMetadataFloat(node->mMetaData, "half_extent_z", pad.halfExtents.z);
319 jumpPadSpawner_.push_back(pad);
320 break;
321 }
322 case 4: // Killzone (lava pit, void, etc.)
323 {
324 const aiMatrix4x4& t = node->mTransformation;
325 glm::vec3 pos = glm::vec3(t.a4, t.b4, t.c4) * kMapAsset.loadScale;
327 kz.pos = pos;
328 kz.halfExtents.x = getMetadataFloat(node->mMetaData, "half_extent_x", kz.halfExtents.x);
329 kz.halfExtents.y = getMetadataFloat(node->mMetaData, "half_extent_y", kz.halfExtents.y);
330 kz.halfExtents.z = getMetadataFloat(node->mMetaData, "half_extent_z", kz.halfExtents.z);
331 killzoneSpawner_.push_back(kz);
332 break;
333 }
334 case 5: // Health pack spawn point
335 {
336 const aiMatrix4x4& t = node->mTransformation;
337 glm::vec3 pos = glm::vec3(t.a4, t.b4, t.c4) * kMapAsset.loadScale;
338 healthPackSpawner_.push_back(HealthPackSpawner{.pos = pos});
339 break;
340 }
341 default:
342 SDL_Log("Unknown entity_type %d on node '%s' — skipping", entity_type, nodeName);
343 break;
344 }
345 }
346 }
347
348 for (unsigned int i = 0; i < node->mNumChildren; i++) {
349 traverse(node->mChildren[i], depth + 1);
350 }
351 };
352
353 traverse(scene->mRootNode, 0);
354
355 std::ofstream out_file("spawn_points.txt");
356
357 if (out_file.is_open()) {
358 out_file << "Spawn Points (" << spawnPoints_.size() << ")\n";
359
360 for (size_t i = 0; i < spawnPoints_.size(); i++) {
361 const glm::vec3& p = spawnPoints_[i].pos;
362
363 out_file << i << ": (" << p.x << ", " << p.y << ", " << p.z << ") yaw=" << spawnPoints_[i].yaw << "\n";
364 }
365 }
366
367 return true;
368}
369
370} // namespace gamemap
Static asset loading and render-default metadata.
const AssetDefinition kMapAsset
Definition AssetCatalog.hpp:27
Load collision geometry (and optionally visual data) from a map GLB file.
PowerupType
Definition PowerupSpawner.hpp:8
Powerup state information.
WeaponType
Weapon type — determines tracer style, damage, sound, and impact effects.
Definition WeaponState.hpp:12
Definition MapConfig.hpp:39
bool loadConfiguredMap(physics::MapCollisionData &out, const char *tag)
Load the configured map's collision into out.
Definition MapConfig.hpp:226
constexpr bool k_useVhacd
Run V-HACD convex decomposition on non-convex prop meshes?
Definition MapConfig.hpp:81
float getMetadataFloat(const aiMetadata *metadata, const std::string &key, float fallback)
Read a numeric custom property as float.
Definition MapConfig.hpp:195
void traverseNodeTree(const aiNode *node, int depth=0)
Definition MapConfig.hpp:160
std::string mapAbsolutePath()
Resolve the absolute path of the configured map file.
Definition MapConfig.hpp:103
std::vector< PowerupSpawner > powerupSpawner_
Definition MapConfig.hpp:129
void * getMetadataValue(const aiMetadata *metadata, const std::string &key)
Definition MapConfig.hpp:177
constexpr bool k_separatedCollisionMap
Does this map use SEPARATED collision and visual meshes?
Definition MapConfig.hpp:53
physics::MapLoadOptions makeLoadOptions()
Build the MapLoadOptions used by both client and server.
Definition MapConfig.hpp:88
std::vector< KillzoneSpawner > killzoneSpawner_
Definition MapConfig.hpp:158
constexpr const char * k_collisionPattern
Substring that identifies collision-only nodes in separated mode.
Definition MapConfig.hpp:57
std::vector< SpawnPoint > spawnPoints_
Definition MapConfig.hpp:115
std::vector< HealthPackSpawner > healthPackSpawner_
Definition MapConfig.hpp:135
std::vector< JumpPadSpawner > jumpPadSpawner_
Definition MapConfig.hpp:148
std::vector< WeaponSpawner > weaponSpawner_
Definition MapConfig.hpp:122
bool loadMapCollision(const std::string &path, MapCollisionData &out, const MapLoadOptions &opts)
API.
Definition MapLoader.cpp:1210
Definition MapConfig.hpp:132
glm::vec3 pos
Definition MapConfig.hpp:133
Jump pad authored in Blender (entity_type = 3).
Definition MapConfig.hpp:143
glm::vec3 pos
Definition MapConfig.hpp:144
glm::vec3 velocity
Definition MapConfig.hpp:145
glm::vec3 halfExtents
Definition MapConfig.hpp:146
Killzone authored in Blender (entity_type = 4).
Definition MapConfig.hpp:154
glm::vec3 pos
Definition MapConfig.hpp:155
glm::vec3 halfExtents
Definition MapConfig.hpp:156
Definition MapConfig.hpp:125
glm::vec3 pos
Definition MapConfig.hpp:127
PowerupType type
Definition MapConfig.hpp:126
A player spawn point: world position plus an authored facing yaw.
Definition MapConfig.hpp:111
float yaw
Facing direction in radians (matches InputSnapshot.yaw: 0 = +Z, atan2(fwd.x, fwd.z)).
Definition MapConfig.hpp:113
glm::vec3 pos
Definition MapConfig.hpp:112
Definition MapConfig.hpp:118
glm::vec3 pos
Definition MapConfig.hpp:120
WeaponType type
Definition MapConfig.hpp:119
Collision data.
Definition MapLoader.hpp:36
std::vector< WorldTriMesh > triMeshes
Definition MapLoader.hpp:42
std::vector< WorldAABB > boxes
Definition MapLoader.hpp:38
std::vector< WorldSphere > spheres
Definition MapLoader.hpp:41
std::vector< WorldBrush > brushes
Definition MapLoader.hpp:39
std::vector< WorldCylinder > cylinders
Definition MapLoader.hpp:40
std::vector< Plane > planes
Definition MapLoader.hpp:37
Load options.
Definition MapLoader.hpp:66
bool allMeshesAreCollision
When true, every mesh in the file is treated as both visual and collision geometry.
Definition MapLoader.hpp:79
std::string collisionCollection
Name of the Blender collection (= Assimp parent node) whose children are collision geometry.
Definition MapLoader.hpp:74
float scale
Uniform scale applied to every vertex position (e.g. 39.37 for m → in).
Definition MapLoader.hpp:68
bool addFloorPlane
When true, an infinite floor plane is added at the lowest Y coordinate found across all collision geo...
Definition MapLoader.hpp:84