118 lines
5.0 KiB
C++
118 lines
5.0 KiB
C++
// 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 <hmap/hmap.h>
|
|
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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<GpuBatch> 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;
|
|
}
|