// Minimal raylib viewer: shows how little code it takes to use a Hammer++ map in another app. // hmap_viewer [file.hmap] WASD + mouse (hold right button) to fly, Shift = fast // // Everything map-related is: loadCompiled() -> iterate batches -> trace()/pointContents(). // Baked vertex lighting is already in Vertex::color, which raylib's default shader multiplies in. #include #include #include #include #include #include "raylib.h" #include "raymath.h" // MatrixIdentity struct GpuBatch { Mesh mesh; int material; }; static Mesh toRaylib(const hmap::Batch& b) { Mesh m = {}; size_t n = b.indices.size(); // expand to a non-indexed triangle list (no 16-bit index limit) m.vertexCount = (int)n; m.triangleCount = (int)(n / 3); m.vertices = (float*)MemAlloc((unsigned)(n * 3 * sizeof(float))); m.texcoords = (float*)MemAlloc((unsigned)(n * 2 * sizeof(float))); m.normals = (float*)MemAlloc((unsigned)(n * 3 * sizeof(float))); m.colors = (unsigned char*)MemAlloc((unsigned)(n * 4)); for (size_t i = 0; i < n; ++i) { const hmap::Vertex& v = b.vertices[b.indices[i]]; m.vertices[i * 3 + 0] = v.pos.x; m.vertices[i * 3 + 1] = v.pos.y; m.vertices[i * 3 + 2] = v.pos.z; m.normals[i * 3 + 0] = v.normal.x; m.normals[i * 3 + 1] = v.normal.y; m.normals[i * 3 + 2] = v.normal.z; m.texcoords[i * 2 + 0] = v.uv.x; m.texcoords[i * 2 + 1] = v.uv.y; m.colors[i * 4 + 0] = (unsigned char)(v.color & 0xff); m.colors[i * 4 + 1] = (unsigned char)((v.color >> 8) & 0xff); m.colors[i * 4 + 2] = (unsigned char)((v.color >> 16) & 0xff); m.colors[i * 4 + 3] = (unsigned char)((v.color >> 24) & 0xff); } UploadMesh(&m, false); return m; } int main(int argc, char** argv) { const char* path = argc > 1 ? argv[1] : "demo_runtime.hmap"; hmap::CompiledMap map; hmap::Status st = hmap::loadCompiled(path, map); // works for source, compiled and mixed files if (!st) { std::fprintf(stderr, "cannot load %s: %s\n", path, st.message.c_str()); return 1; } InitWindow(1280, 720, "hmap viewer"); SetTargetFPS(120); std::vector gpu; for (const hmap::Batch& b : map.batches) if (!b.indices.empty()) gpu.push_back({toRaylib(b), b.material}); Material mat = LoadMaterialDefault(); hmap::Vec3 pos(0, 100, 300); float yaw = 0, pitch = 0; for (const hmap::Entity& e : map.entities) if (e.classname == "info_player_start") { pos = e.origin + hmap::Vec3(0, 40, 0); yaw = e.angles.y; } while (!WindowShouldClose()) { float dt = GetFrameTime(); if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) { Vector2 d = GetMouseDelta(); yaw -= d.x * 0.15f; pitch = std::fmax(-89.0f, std::fmin(89.0f, pitch - d.y * 0.15f)); } float cy = std::cos(yaw * hmap::kDeg2Rad), sy = std::sin(yaw * hmap::kDeg2Rad); float cp = std::cos(pitch * hmap::kDeg2Rad), sp = std::sin(pitch * hmap::kDeg2Rad); hmap::Vec3 fwd(-sy * cp, sp, -cy * cp), right(cy, 0, -sy); float speed = (IsKeyDown(KEY_LEFT_SHIFT) ? 900.0f : 300.0f) * dt; hmap::Vec3 move; if (IsKeyDown(KEY_W)) move += fwd; if (IsKeyDown(KEY_S)) move -= fwd; if (IsKeyDown(KEY_D)) move += right; if (IsKeyDown(KEY_A)) move -= right; hmap::Vec3 next = pos + hmap::normalize(move) * speed; // collide against the BSP: stop at walls if (!map.trace(pos, next + hmap::normalize(move) * 12.0f).hit) pos = next; hmap::TraceResult aim = map.trace(pos, pos + fwd * 4000.0f); // "what am I looking at?" Camera3D cam = {}; cam.position = {pos.x, pos.y, pos.z}; cam.target = {pos.x + fwd.x, pos.y + fwd.y, pos.z + fwd.z}; cam.up = {0, 1, 0}; cam.fovy = 70; cam.projection = CAMERA_PERSPECTIVE; BeginDrawing(); ClearBackground({24, 26, 32, 255}); BeginMode3D(cam); for (const GpuBatch& g : gpu) { const hmap::Material& hm = map.materials[(size_t)g.material]; mat.maps[MATERIAL_MAP_DIFFUSE].color = {(unsigned char)(hm.color.x * 255), (unsigned char)(hm.color.y * 255), (unsigned char)(hm.color.z * 255), 255}; DrawMesh(g.mesh, mat, MatrixIdentity()); } for (const hmap::Entity& e : map.entities) DrawCubeWires({e.origin.x, e.origin.y, e.origin.z}, 12, 12, 12, e.classname.rfind("light", 0) == 0 ? YELLOW : SKYBLUE); if (aim.hit) DrawSphere({aim.point.x, aim.point.y, aim.point.z}, 3, RED); EndMode3D(); DrawText(TextFormat("%s | %d batches %u tris | aim: %s | %d fps", path, (int)gpu.size(), map.stats.triangles, aim.hit ? TextFormat("%.0f units", aim.fraction * 4000.0f) : "nothing", GetFPS()), 10, 10, 18, RAYWHITE); EndDrawing(); } for (GpuBatch& g : gpu) UnloadMesh(g.mesh); MemFree(mat.maps); CloseWindow(); return 0; }