TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1039 lines
30 KiB
1039 lines
30 KiB
#include <phpx.h>
|
|
|
|
#include <windows.h>
|
|
#include <gl/GL.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cwchar>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <windowsx.h>
|
|
|
|
extern "C" unsigned lodepng_decode32_file(unsigned char **out, unsigned *w, unsigned *h, const char *filename);
|
|
extern "C" const char *lodepng_error_text(unsigned code);
|
|
|
|
using namespace php;
|
|
|
|
struct BlockKey {
|
|
int x;
|
|
int y;
|
|
int z;
|
|
|
|
bool operator==(const BlockKey &other) const {
|
|
return x == other.x && y == other.y && z == other.z;
|
|
}
|
|
};
|
|
|
|
struct BlockKeyHash {
|
|
size_t operator()(const BlockKey &k) const {
|
|
const uint64_t x = static_cast<uint32_t>(k.x);
|
|
const uint64_t y = static_cast<uint32_t>(k.y);
|
|
const uint64_t z = static_cast<uint32_t>(k.z);
|
|
return static_cast<size_t>((x * 73856093u) ^ (y * 19349663u) ^ (z * 83492791u));
|
|
}
|
|
};
|
|
|
|
struct ChunkKey {
|
|
int x;
|
|
int z;
|
|
|
|
bool operator==(const ChunkKey &other) const {
|
|
return x == other.x && z == other.z;
|
|
}
|
|
};
|
|
|
|
struct ChunkKeyHash {
|
|
size_t operator()(const ChunkKey &k) const {
|
|
const uint64_t x = static_cast<uint32_t>(k.x);
|
|
const uint64_t z = static_cast<uint32_t>(k.z);
|
|
return static_cast<size_t>((x * 73856093u) ^ (z * 83492791u));
|
|
}
|
|
};
|
|
|
|
struct Vertex {
|
|
float x, y, z;
|
|
float nx, ny, nz;
|
|
float u, v;
|
|
float shade;
|
|
};
|
|
|
|
struct Face {
|
|
Vertex v[4];
|
|
bool water;
|
|
bool cloud;
|
|
};
|
|
|
|
struct Chunk {
|
|
std::unordered_map<BlockKey, int, BlockKeyHash> blocks;
|
|
std::vector<Face> faces;
|
|
int min_y = 0;
|
|
int max_y = 0;
|
|
GLuint opaque_list = 0;
|
|
GLuint transparent_list = 0;
|
|
};
|
|
|
|
static HWND g_hwnd = nullptr;
|
|
static HDC g_hdc = nullptr;
|
|
static HGLRC g_glrc = nullptr;
|
|
static bool g_should_close = false;
|
|
static bool g_keys[256] = {};
|
|
static bool g_mouse_captured = false;
|
|
static bool g_mouse_ready = false;
|
|
static POINT g_last_mouse = {};
|
|
static double g_mouse_dx = 0.0;
|
|
static double g_mouse_dy = 0.0;
|
|
static GLuint g_atlas_texture = 0;
|
|
static GLuint g_sky_texture = 0;
|
|
static int g_width = 1280;
|
|
static int g_height = 720;
|
|
static float g_camera_x = 8.0f;
|
|
static float g_camera_y = 9.0f;
|
|
static float g_camera_z = 18.0f;
|
|
static float g_camera_yaw = -2.35f;
|
|
static float g_camera_pitch = -0.25f;
|
|
static std::unordered_map<BlockKey, int, BlockKeyHash> g_blocks;
|
|
static std::vector<Face> g_faces;
|
|
static std::unordered_map<ChunkKey, Chunk, ChunkKeyHash> g_chunks;
|
|
static std::unordered_map<BlockKey, int, BlockKeyHash> g_pending_chunk_blocks;
|
|
static ChunkKey g_pending_chunk_key{0, 0};
|
|
static bool g_has_pending_chunk = false;
|
|
|
|
static const int BLOCK_WATER = 64;
|
|
static const int BLOCK_CLOUD = 16;
|
|
static const int CHUNK_SIZE = 16;
|
|
|
|
static void delete_chunk_lists(Chunk &chunk) {
|
|
if (chunk.opaque_list) {
|
|
glDeleteLists(chunk.opaque_list, 1);
|
|
chunk.opaque_list = 0;
|
|
}
|
|
if (chunk.transparent_list) {
|
|
glDeleteLists(chunk.transparent_list, 1);
|
|
chunk.transparent_list = 0;
|
|
}
|
|
}
|
|
|
|
static void update_cursor_clip() {
|
|
if (!g_hwnd) {
|
|
return;
|
|
}
|
|
RECT rect{};
|
|
GetClientRect(g_hwnd, &rect);
|
|
POINT top_left{rect.left, rect.top};
|
|
POINT bottom_right{rect.right, rect.bottom};
|
|
ClientToScreen(g_hwnd, &top_left);
|
|
ClientToScreen(g_hwnd, &bottom_right);
|
|
rect.left = top_left.x;
|
|
rect.top = top_left.y;
|
|
rect.right = bottom_right.x;
|
|
rect.bottom = bottom_right.y;
|
|
ClipCursor(&rect);
|
|
}
|
|
|
|
static void enable_mouse_capture() {
|
|
if (!g_hwnd) {
|
|
return;
|
|
}
|
|
if (g_mouse_captured) {
|
|
update_cursor_clip();
|
|
return;
|
|
}
|
|
SetCapture(g_hwnd);
|
|
ShowCursor(FALSE);
|
|
update_cursor_clip();
|
|
g_mouse_captured = true;
|
|
}
|
|
|
|
static void disable_mouse_capture() {
|
|
if (!g_mouse_captured) {
|
|
return;
|
|
}
|
|
ClipCursor(nullptr);
|
|
ReleaseCapture();
|
|
ShowCursor(TRUE);
|
|
g_mouse_captured = false;
|
|
}
|
|
|
|
static const int BLOCK_TILES[65][6] = {
|
|
{0, 0, 0, 0, 0, 0},
|
|
{16, 16, 32, 0, 16, 16}, // grass
|
|
{1, 1, 1, 1, 1, 1}, // sand
|
|
{2, 2, 2, 2, 2, 2}, // stone
|
|
{3, 3, 3, 3, 3, 3},
|
|
{20, 20, 36, 4, 20, 20}, // wood
|
|
{5, 5, 5, 5, 5, 5},
|
|
{6, 6, 6, 6, 6, 6}, // dirt
|
|
{7, 7, 7, 7, 7, 7},
|
|
{24, 24, 40, 8, 24, 24}, // snow
|
|
{9, 9, 9, 9, 9, 9},
|
|
{10, 10, 10, 10, 10, 10}, // cobble
|
|
{11, 11, 11, 11, 11, 11},
|
|
{12, 12, 12, 12, 12, 12},
|
|
{13, 13, 13, 13, 13, 13},
|
|
{14, 14, 14, 14, 14, 14}, // leaves
|
|
{15, 15, 15, 15, 15, 15}, // cloud
|
|
};
|
|
|
|
static const int PLANT_TILES[24] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
48, // tall grass
|
|
49, // yellow flower
|
|
50, // red flower
|
|
51, // purple flower
|
|
52, // sun flower
|
|
53, // white flower
|
|
54, // blue flower
|
|
};
|
|
|
|
static LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
|
switch (msg) {
|
|
case WM_CLOSE:
|
|
case WM_DESTROY:
|
|
g_should_close = true;
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
case WM_SIZE:
|
|
g_width = LOWORD(lp);
|
|
g_height = std::max(1, static_cast<int>(HIWORD(lp)));
|
|
update_cursor_clip();
|
|
return 0;
|
|
case WM_ACTIVATE:
|
|
if (LOWORD(wp) == WA_INACTIVE) {
|
|
disable_mouse_capture();
|
|
} else {
|
|
enable_mouse_capture();
|
|
}
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
if (wp < 256) {
|
|
g_keys[wp] = true;
|
|
}
|
|
return 0;
|
|
case WM_KEYUP:
|
|
if (wp < 256) {
|
|
g_keys[wp] = false;
|
|
}
|
|
return 0;
|
|
case WM_MOUSEMOVE: {
|
|
POINT p{GET_X_LPARAM(lp), GET_Y_LPARAM(lp)};
|
|
if (g_mouse_ready) {
|
|
g_mouse_dx += static_cast<double>(p.x - g_last_mouse.x);
|
|
g_mouse_dy += static_cast<double>(p.y - g_last_mouse.y);
|
|
}
|
|
g_last_mouse = p;
|
|
g_mouse_ready = true;
|
|
return 0;
|
|
}
|
|
}
|
|
return DefWindowProc(hwnd, msg, wp, lp);
|
|
}
|
|
|
|
static void set_perspective(double fov_y, double aspect, double near_z, double far_z) {
|
|
const double top = near_z * std::tan(fov_y * 3.14159265358979323846 / 360.0);
|
|
const double bottom = -top;
|
|
const double right = top * aspect;
|
|
const double left = -right;
|
|
glFrustum(left, right, bottom, top, near_z, far_z);
|
|
}
|
|
|
|
static void draw_sky_gradient() {
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glOrtho(-1, 1, -1, 1, -1, 1);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_FOG);
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
if (g_sky_texture) {
|
|
const float u = std::fmod(g_camera_yaw * -0.08f, 1.0f);
|
|
const float v = std::max(-0.22f, std::min(0.22f, g_camera_pitch * 0.12f));
|
|
glEnable(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, g_sky_texture);
|
|
glColor3f(1.0f, 1.0f, 1.0f);
|
|
glBegin(GL_QUADS);
|
|
glTexCoord2f(u, 0.15f + v);
|
|
glVertex2f(-1.0f, -1.0f);
|
|
glTexCoord2f(u + 1.0f, 0.15f + v);
|
|
glVertex2f(1.0f, -1.0f);
|
|
glTexCoord2f(u + 1.0f, 0.88f + v);
|
|
glVertex2f(1.0f, 1.0f);
|
|
glTexCoord2f(u, 0.88f + v);
|
|
glVertex2f(-1.0f, 1.0f);
|
|
glEnd();
|
|
} else {
|
|
glDisable(GL_TEXTURE_2D);
|
|
glBegin(GL_QUADS);
|
|
glColor3f(0.68f, 0.84f, 0.96f);
|
|
glVertex2f(-1.0f, -1.0f);
|
|
glVertex2f(1.0f, -1.0f);
|
|
glColor3f(0.32f, 0.62f, 0.90f);
|
|
glVertex2f(1.0f, 1.0f);
|
|
glVertex2f(-1.0f, 1.0f);
|
|
glEnd();
|
|
}
|
|
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_FOG);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
glPopMatrix();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
}
|
|
|
|
static void draw_crosshair() {
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glOrtho(-1, 1, -1, 1, -1, 1);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_FOG);
|
|
glDisable(GL_CULL_FACE);
|
|
glDisable(GL_BLEND);
|
|
|
|
const float aspect = static_cast<float>(std::max(1, g_height)) / static_cast<float>(std::max(1, g_width));
|
|
const float size_x = 0.018f;
|
|
const float size_y = size_x / std::max(0.1f, aspect);
|
|
const float gap_x = 0.006f;
|
|
const float gap_y = gap_x / std::max(0.1f, aspect);
|
|
|
|
glColor3f(0.08f, 0.10f, 0.12f);
|
|
glLineWidth(2.0f);
|
|
glBegin(GL_LINES);
|
|
glVertex2f(-size_x, 0.0f);
|
|
glVertex2f(-gap_x, 0.0f);
|
|
glVertex2f(gap_x, 0.0f);
|
|
glVertex2f(size_x, 0.0f);
|
|
glVertex2f(0.0f, -size_y);
|
|
glVertex2f(0.0f, -gap_y);
|
|
glVertex2f(0.0f, gap_y);
|
|
glVertex2f(0.0f, size_y);
|
|
glEnd();
|
|
glLineWidth(1.0f);
|
|
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_FOG);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
glPopMatrix();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
}
|
|
|
|
static bool load_texture_into(const char *path, GLuint *texture, bool nearest, bool transparent_magenta) {
|
|
unsigned char *data = nullptr;
|
|
unsigned int width = 0;
|
|
unsigned int height = 0;
|
|
const unsigned int error = lodepng_decode32_file(&data, &width, &height, path);
|
|
if (error != 0) {
|
|
std::fprintf(stderr, "load texture failed: %s: %s\n", path, lodepng_error_text(error));
|
|
return false;
|
|
}
|
|
|
|
std::vector<unsigned char> flipped(width * height * 4);
|
|
for (unsigned int y = 0; y < height; y++) {
|
|
const unsigned int src_y = y;
|
|
const unsigned int dst_y = height - y - 1;
|
|
std::memcpy(&flipped[dst_y * width * 4], data + src_y * width * 4, width * 4);
|
|
}
|
|
std::free(data);
|
|
|
|
if (transparent_magenta) {
|
|
for (unsigned int i = 0; i < width * height; i++) {
|
|
unsigned char *p = &flipped[i * 4];
|
|
if (p[0] == 255 && p[1] == 0 && p[2] == 255) {
|
|
p[3] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*texture) {
|
|
glDeleteTextures(1, texture);
|
|
*texture = 0;
|
|
}
|
|
glGenTextures(1, texture);
|
|
glBindTexture(GL_TEXTURE_2D, *texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest ? GL_NEAREST : GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest ? GL_NEAREST : GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, flipped.data());
|
|
return true;
|
|
}
|
|
|
|
static bool load_texture(const char *path) {
|
|
return load_texture_into(path, &g_atlas_texture, true, true);
|
|
}
|
|
|
|
static bool has_block(int x, int y, int z) {
|
|
return g_blocks.find(BlockKey{x, y, z}) != g_blocks.end();
|
|
}
|
|
|
|
static int get_block(int x, int y, int z) {
|
|
auto it = g_blocks.find(BlockKey{x, y, z});
|
|
return it == g_blocks.end() ? 0 : it->second;
|
|
}
|
|
|
|
static bool is_plant(int type) {
|
|
return type >= 17 && type <= 23;
|
|
}
|
|
|
|
static bool is_transparent_block(int type) {
|
|
return type == 0 || type == BLOCK_WATER || type == BLOCK_CLOUD || is_plant(type) || type == 15;
|
|
}
|
|
|
|
static void tile_uv(int tile, float out[4][2]) {
|
|
const float s = 1.0f / 16.0f;
|
|
const float a = 1.0f / 2048.0f;
|
|
const float b = s - 1.0f / 2048.0f;
|
|
const float du = static_cast<float>(tile % 16) * s;
|
|
const float dv = static_cast<float>(tile / 16) * s;
|
|
out[0][0] = du + a; out[0][1] = dv + b;
|
|
out[1][0] = du + a; out[1][1] = dv + a;
|
|
out[2][0] = du + b; out[2][1] = dv + a;
|
|
out[3][0] = du + b; out[3][1] = dv + b;
|
|
}
|
|
|
|
static void add_face(
|
|
int x, int y, int z, int type, int face_index,
|
|
const float normal[3], const float corners[4][3], bool water)
|
|
{
|
|
static const float shades[6] = {0.76f, 0.76f, 1.0f, 0.48f, 0.68f, 0.68f};
|
|
float uv[4][2] = {};
|
|
int tile = 0;
|
|
if (!water && type >= 0 && type < 65) {
|
|
tile = BLOCK_TILES[type][face_index];
|
|
}
|
|
tile_uv(tile, uv);
|
|
|
|
Face face{};
|
|
face.water = water;
|
|
face.cloud = type == BLOCK_CLOUD;
|
|
for (int i = 0; i < 4; i++) {
|
|
face.v[i].x = static_cast<float>(x) + corners[i][0];
|
|
face.v[i].y = static_cast<float>(y) + corners[i][1];
|
|
face.v[i].z = static_cast<float>(z) + corners[i][2];
|
|
face.v[i].nx = normal[0];
|
|
face.v[i].ny = normal[1];
|
|
face.v[i].nz = normal[2];
|
|
face.v[i].u = uv[i][0];
|
|
face.v[i].v = uv[i][1];
|
|
face.v[i].shade = water ? 1.0f : shades[face_index];
|
|
}
|
|
g_faces.push_back(face);
|
|
}
|
|
|
|
static void add_plant_face(int x, int y, int z, int type, const float corners[4][3], const float normal[3]) {
|
|
float uv[4][2] = {};
|
|
const int tile = type >= 0 && type < 24 ? PLANT_TILES[type] : 48;
|
|
tile_uv(tile, uv);
|
|
Face face{};
|
|
face.water = false;
|
|
face.cloud = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
face.v[i].x = static_cast<float>(x) + corners[i][0];
|
|
face.v[i].y = static_cast<float>(y) + corners[i][1];
|
|
face.v[i].z = static_cast<float>(z) + corners[i][2];
|
|
face.v[i].nx = normal[0];
|
|
face.v[i].ny = normal[1];
|
|
face.v[i].nz = normal[2];
|
|
face.v[i].u = uv[i][0];
|
|
face.v[i].v = uv[i][1];
|
|
face.v[i].shade = 0.92f;
|
|
}
|
|
g_faces.push_back(face);
|
|
}
|
|
|
|
static void build_plant_faces(int x, int y, int z, int type) {
|
|
const float h = 0.46f;
|
|
const float bottom = -0.5f;
|
|
const float top = 0.55f;
|
|
const float n1[3] = {1, 0, 1};
|
|
const float n2[3] = {1, 0, -1};
|
|
const float a[4][3] = {{-h, bottom, -h}, {-h, top, -h}, {h, top, h}, {h, bottom, h}};
|
|
const float b[4][3] = {{h, bottom, -h}, {h, top, -h}, {-h, top, h}, {-h, bottom, h}};
|
|
const float ar[4][3] = {{h, bottom, h}, {h, top, h}, {-h, top, -h}, {-h, bottom, -h}};
|
|
const float br[4][3] = {{-h, bottom, h}, {-h, top, h}, {h, top, -h}, {h, bottom, -h}};
|
|
add_plant_face(x, y, z, type, a, n1);
|
|
add_plant_face(x, y, z, type, b, n2);
|
|
add_plant_face(x, y, z, type, ar, n1);
|
|
add_plant_face(x, y, z, type, br, n2);
|
|
}
|
|
|
|
static void build_faces_for_block(int x, int y, int z, int type) {
|
|
const float h = 0.5f;
|
|
const float water_y = 0.32f;
|
|
if (is_plant(type)) {
|
|
build_plant_faces(x, y, z, type);
|
|
return;
|
|
}
|
|
if (type == BLOCK_WATER) {
|
|
if (!has_block(x, y + 1, z)) {
|
|
const float normal[3] = {0, 1, 0};
|
|
const float corners[4][3] = {{-h, water_y, -h}, {-h, water_y, h}, {h, water_y, h}, {h, water_y, -h}};
|
|
add_face(x, y, z, type, 2, normal, corners, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
struct Def {
|
|
int dx, dy, dz;
|
|
int face;
|
|
float n[3];
|
|
float c[4][3];
|
|
};
|
|
const Def defs[6] = {
|
|
{-1, 0, 0, 0, {-1, 0, 0}, {{-h, -h, h}, {-h, h, h}, {-h, h, -h}, {-h, -h, -h}}},
|
|
{1, 0, 0, 1, {1, 0, 0}, {{h, -h, -h}, {h, h, -h}, {h, h, h}, {h, -h, h}}},
|
|
{0, 1, 0, 2, {0, 1, 0}, {{-h, h, -h}, {-h, h, h}, {h, h, h}, {h, h, -h}}},
|
|
{0, -1, 0, 3, {0, -1, 0}, {{-h, -h, h}, {-h, -h, -h}, {h, -h, -h}, {h, -h, h}}},
|
|
{0, 0, 1, 4, {0, 0, 1}, {{h, -h, h}, {h, h, h}, {-h, h, h}, {-h, -h, h}}},
|
|
{0, 0, -1, 5, {0, 0, -1}, {{-h, -h, -h}, {-h, h, -h}, {h, h, -h}, {h, -h, -h}}},
|
|
};
|
|
for (const Def &d : defs) {
|
|
const int neighbor = get_block(x + d.dx, y + d.dy, z + d.dz);
|
|
if (is_transparent_block(neighbor)) {
|
|
add_face(x, y, z, type, d.face, d.n, d.c, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_face(const Face &face);
|
|
|
|
static GLuint build_face_list(const std::vector<Face> &faces, bool transparent) {
|
|
GLuint list = glGenLists(1);
|
|
if (!list) {
|
|
return 0;
|
|
}
|
|
glNewList(list, GL_COMPILE);
|
|
for (const Face &face : faces) {
|
|
const bool is_transparent = face.water || face.cloud;
|
|
if (is_transparent == transparent) {
|
|
draw_face(face);
|
|
}
|
|
}
|
|
glEndList();
|
|
return list;
|
|
}
|
|
|
|
static void update_chunk_bounds(Chunk &chunk) {
|
|
if (chunk.blocks.empty()) {
|
|
chunk.min_y = 0;
|
|
chunk.max_y = 0;
|
|
return;
|
|
}
|
|
int min_y = 32767;
|
|
int max_y = -32768;
|
|
for (const auto &entry : chunk.blocks) {
|
|
min_y = std::min(min_y, entry.first.y);
|
|
max_y = std::max(max_y, entry.first.y);
|
|
}
|
|
chunk.min_y = min_y - 1;
|
|
chunk.max_y = max_y + 1;
|
|
}
|
|
|
|
static float chunk_distance_sq(const ChunkKey &key) {
|
|
const float center_x = static_cast<float>(key.x * CHUNK_SIZE) + CHUNK_SIZE * 0.5f;
|
|
const float center_z = static_cast<float>(key.z * CHUNK_SIZE) + CHUNK_SIZE * 0.5f;
|
|
const float dx = center_x - g_camera_x;
|
|
const float dz = center_z - g_camera_z;
|
|
return dx * dx + dz * dz;
|
|
}
|
|
|
|
static bool chunk_visible_from_camera(const ChunkKey &key, const Chunk &chunk) {
|
|
if (chunk.faces.empty()) {
|
|
return false;
|
|
}
|
|
|
|
const float center_x = static_cast<float>(key.x * CHUNK_SIZE) + CHUNK_SIZE * 0.5f;
|
|
const float center_y = static_cast<float>(chunk.min_y + chunk.max_y) * 0.5f;
|
|
const float center_z = static_cast<float>(key.z * CHUNK_SIZE) + CHUNK_SIZE * 0.5f;
|
|
const float dx = center_x - g_camera_x;
|
|
const float dy = center_y - g_camera_y;
|
|
const float dz = center_z - g_camera_z;
|
|
const float dist_sq = dx * dx + dy * dy + dz * dz;
|
|
const float radius = 28.0f;
|
|
if (dist_sq <= radius * radius) {
|
|
return true;
|
|
}
|
|
if (dist_sq > 156.0f * 156.0f) {
|
|
return false;
|
|
}
|
|
|
|
const float forward_x = -std::sin(g_camera_yaw);
|
|
const float forward_y = std::sin(g_camera_pitch) * 0.65f;
|
|
const float forward_z = -std::cos(g_camera_yaw);
|
|
const float dot = dx * forward_x + dy * forward_y + dz * forward_z;
|
|
if (dot < -radius) {
|
|
return false;
|
|
}
|
|
|
|
const float horizontal_dist_sq = dx * dx + dz * dz;
|
|
if (horizontal_dist_sq <= 0.001f) {
|
|
return true;
|
|
}
|
|
const float side_x = std::cos(g_camera_yaw);
|
|
const float side_z = -std::sin(g_camera_yaw);
|
|
const float side = std::fabs(dx * side_x + dz * side_z);
|
|
return side <= std::sqrt(horizontal_dist_sq) * 0.96f + radius;
|
|
}
|
|
|
|
static void rebuild_chunk(int cx, int cz) {
|
|
const ChunkKey key{cx, cz};
|
|
auto it = g_chunks.find(key);
|
|
if (it == g_chunks.end()) {
|
|
return;
|
|
}
|
|
|
|
Chunk &chunk = it->second;
|
|
delete_chunk_lists(chunk);
|
|
update_chunk_bounds(chunk);
|
|
chunk.faces.clear();
|
|
chunk.faces.reserve(chunk.blocks.size() * 3);
|
|
const size_t old_count = g_faces.size();
|
|
for (const auto &entry : chunk.blocks) {
|
|
build_faces_for_block(entry.first.x, entry.first.y, entry.first.z, entry.second);
|
|
}
|
|
chunk.faces.assign(g_faces.begin() + old_count, g_faces.end());
|
|
g_faces.resize(old_count);
|
|
chunk.opaque_list = build_face_list(chunk.faces, false);
|
|
chunk.transparent_list = build_face_list(chunk.faces, true);
|
|
}
|
|
|
|
static void rebuild_chunk_and_neighbors(int cx, int cz) {
|
|
rebuild_chunk(cx, cz);
|
|
rebuild_chunk(cx - 1, cz);
|
|
rebuild_chunk(cx + 1, cz);
|
|
rebuild_chunk(cx, cz - 1);
|
|
rebuild_chunk(cx, cz + 1);
|
|
}
|
|
|
|
static void draw_face(const Face &face) {
|
|
if (face.water) {
|
|
glColor4f(0.12f, 0.48f, 0.82f, 0.58f);
|
|
glBegin(GL_QUADS);
|
|
for (const Vertex &v : face.v) {
|
|
glVertex3f(v.x, v.y, v.z);
|
|
}
|
|
glEnd();
|
|
return;
|
|
}
|
|
if (face.cloud) {
|
|
glColor4f(1.0f, 1.0f, 1.0f, 0.72f);
|
|
glBegin(GL_QUADS);
|
|
for (const Vertex &v : face.v) {
|
|
glVertex3f(v.x, v.y, v.z);
|
|
}
|
|
glEnd();
|
|
return;
|
|
}
|
|
glBegin(GL_QUADS);
|
|
for (const Vertex &v : face.v) {
|
|
glColor4f(v.shade, v.shade, v.shade, 1.0f);
|
|
glNormal3f(v.nx, v.ny, v.nz);
|
|
glTexCoord2f(v.u, v.v);
|
|
glVertex3f(v.x, v.y, v.z);
|
|
}
|
|
glEnd();
|
|
}
|
|
|
|
Bool php_craft_init(String title, Int width, Int height, String texture_path) {
|
|
SetProcessDPIAware();
|
|
SetConsoleOutputCP(65001);
|
|
g_width = static_cast<int>(width);
|
|
g_height = static_cast<int>(height);
|
|
|
|
WNDCLASS wc{};
|
|
wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = wnd_proc;
|
|
wc.hInstance = GetModuleHandle(nullptr);
|
|
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wc.lpszClassName = "TypePhpCraftWindow";
|
|
RegisterClass(&wc);
|
|
|
|
g_hwnd = CreateWindowEx(
|
|
0, wc.lpszClassName, title.data(),
|
|
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
|
CW_USEDEFAULT, CW_USEDEFAULT, g_width, g_height,
|
|
nullptr, nullptr, wc.hInstance, nullptr);
|
|
if (!g_hwnd) {
|
|
return false;
|
|
}
|
|
|
|
g_hdc = GetDC(g_hwnd);
|
|
PIXELFORMATDESCRIPTOR pfd{};
|
|
pfd.nSize = sizeof(pfd);
|
|
pfd.nVersion = 1;
|
|
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
|
pfd.iPixelType = PFD_TYPE_RGBA;
|
|
pfd.cColorBits = 32;
|
|
pfd.cDepthBits = 24;
|
|
pfd.cAlphaBits = 8;
|
|
pfd.iLayerType = PFD_MAIN_PLANE;
|
|
const int pf = ChoosePixelFormat(g_hdc, &pfd);
|
|
SetPixelFormat(g_hdc, pf, &pfd);
|
|
|
|
g_glrc = wglCreateContext(g_hdc);
|
|
wglMakeCurrent(g_hdc, g_glrc);
|
|
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_ALPHA_TEST);
|
|
glAlphaFunc(GL_GREATER, 0.4f);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glEnable(GL_FOG);
|
|
const GLfloat fog_color[] = {0.68f, 0.84f, 0.96f, 1.0f};
|
|
glFogfv(GL_FOG_COLOR, fog_color);
|
|
glFogi(GL_FOG_MODE, GL_LINEAR);
|
|
glFogf(GL_FOG_START, 72.0f);
|
|
glFogf(GL_FOG_END, 118.0f);
|
|
glClearColor(0.68f, 0.84f, 0.96f, 1.0f);
|
|
|
|
enable_mouse_capture();
|
|
return load_texture(texture_path.data());
|
|
}
|
|
|
|
Bool php_craft_set_sky_texture(String texture_path) {
|
|
if (!load_texture_into(texture_path.data(), &g_sky_texture, false, false)) {
|
|
return false;
|
|
}
|
|
glBindTexture(GL_TEXTURE_2D, g_sky_texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
|
|
return true;
|
|
}
|
|
|
|
void php_craft_shutdown() {
|
|
disable_mouse_capture();
|
|
for (auto &entry : g_chunks) {
|
|
delete_chunk_lists(entry.second);
|
|
}
|
|
g_chunks.clear();
|
|
if (g_atlas_texture) {
|
|
glDeleteTextures(1, &g_atlas_texture);
|
|
g_atlas_texture = 0;
|
|
}
|
|
if (g_sky_texture) {
|
|
glDeleteTextures(1, &g_sky_texture);
|
|
g_sky_texture = 0;
|
|
}
|
|
if (g_glrc) {
|
|
wglMakeCurrent(nullptr, nullptr);
|
|
wglDeleteContext(g_glrc);
|
|
g_glrc = nullptr;
|
|
}
|
|
if (g_hwnd && g_hdc) {
|
|
ReleaseDC(g_hwnd, g_hdc);
|
|
g_hdc = nullptr;
|
|
}
|
|
if (g_hwnd) {
|
|
DestroyWindow(g_hwnd);
|
|
g_hwnd = nullptr;
|
|
}
|
|
}
|
|
|
|
Bool php_craft_should_close() {
|
|
return g_should_close;
|
|
}
|
|
|
|
void php_craft_poll_events() {
|
|
MSG msg;
|
|
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
void php_craft_begin_world() {
|
|
for (auto &entry : g_chunks) {
|
|
delete_chunk_lists(entry.second);
|
|
}
|
|
g_blocks.clear();
|
|
g_faces.clear();
|
|
g_chunks.clear();
|
|
g_pending_chunk_blocks.clear();
|
|
g_has_pending_chunk = false;
|
|
}
|
|
|
|
void php_craft_set_block(Int x, Int y, Int z, Int type) {
|
|
const int t = static_cast<int>(type);
|
|
if (t != 0) {
|
|
g_blocks[BlockKey{static_cast<int>(x), static_cast<int>(y), static_cast<int>(z)}] = t;
|
|
}
|
|
}
|
|
|
|
void php_craft_build_mesh() {
|
|
g_faces.clear();
|
|
g_faces.reserve(g_blocks.size() * 3);
|
|
for (const auto &entry : g_blocks) {
|
|
build_faces_for_block(entry.first.x, entry.first.y, entry.first.z, entry.second);
|
|
}
|
|
std::printf("Craft mesh: blocks=%zu faces=%zu\n", g_blocks.size(), g_faces.size());
|
|
std::fflush(stdout);
|
|
}
|
|
|
|
void php_craft_begin_chunk(Int chunk_x, Int chunk_z) {
|
|
g_pending_chunk_key = ChunkKey{static_cast<int>(chunk_x), static_cast<int>(chunk_z)};
|
|
g_pending_chunk_blocks.clear();
|
|
g_has_pending_chunk = true;
|
|
}
|
|
|
|
void php_craft_set_chunk_block(Int x, Int y, Int z, Int type) {
|
|
if (!g_has_pending_chunk) {
|
|
return;
|
|
}
|
|
const int t = static_cast<int>(type);
|
|
if (t != 0) {
|
|
g_pending_chunk_blocks[BlockKey{static_cast<int>(x), static_cast<int>(y), static_cast<int>(z)}] = t;
|
|
}
|
|
}
|
|
|
|
void php_craft_commit_chunk(Int chunk_x, Int chunk_z) {
|
|
const int cx = static_cast<int>(chunk_x);
|
|
const int cz = static_cast<int>(chunk_z);
|
|
const ChunkKey key{cx, cz};
|
|
|
|
auto old = g_chunks.find(key);
|
|
if (old != g_chunks.end()) {
|
|
for (const auto &entry : old->second.blocks) {
|
|
g_blocks.erase(entry.first);
|
|
}
|
|
}
|
|
|
|
Chunk &chunk = g_chunks[key];
|
|
delete_chunk_lists(chunk);
|
|
chunk.blocks = g_pending_chunk_blocks;
|
|
chunk.faces.clear();
|
|
for (const auto &entry : chunk.blocks) {
|
|
g_blocks[entry.first] = entry.second;
|
|
}
|
|
|
|
g_pending_chunk_blocks.clear();
|
|
g_has_pending_chunk = false;
|
|
rebuild_chunk_and_neighbors(cx, cz);
|
|
}
|
|
|
|
void php_craft_remove_chunk(Int chunk_x, Int chunk_z) {
|
|
const int cx = static_cast<int>(chunk_x);
|
|
const int cz = static_cast<int>(chunk_z);
|
|
const ChunkKey key{cx, cz};
|
|
auto it = g_chunks.find(key);
|
|
if (it == g_chunks.end()) {
|
|
return;
|
|
}
|
|
for (const auto &entry : it->second.blocks) {
|
|
g_blocks.erase(entry.first);
|
|
}
|
|
delete_chunk_lists(it->second);
|
|
g_chunks.erase(it);
|
|
rebuild_chunk(cx - 1, cz);
|
|
rebuild_chunk(cx + 1, cz);
|
|
rebuild_chunk(cx, cz - 1);
|
|
rebuild_chunk(cx, cz + 1);
|
|
}
|
|
|
|
void php_craft_render_frame() {
|
|
SetWindowTextA(g_hwnd, "TypePHP Minecraft Demo - Craft/OpenGL");
|
|
glViewport(0, 0, g_width, std::max(1, g_height));
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
draw_sky_gradient();
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
set_perspective(70.0, static_cast<double>(g_width) / std::max(1, g_height), 0.1, 220.0);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
glRotatef(-g_camera_pitch * 57.2957795f, 1, 0, 0);
|
|
glRotatef(-g_camera_yaw * 57.2957795f, 0, 1, 0);
|
|
glTranslatef(-g_camera_x, -g_camera_y, -g_camera_z);
|
|
|
|
struct VisibleChunk {
|
|
const Chunk *chunk;
|
|
float distance_sq;
|
|
};
|
|
std::vector<VisibleChunk> visible_chunks;
|
|
visible_chunks.reserve(g_chunks.size());
|
|
for (const auto &chunk_entry : g_chunks) {
|
|
if (chunk_visible_from_camera(chunk_entry.first, chunk_entry.second)) {
|
|
visible_chunks.push_back(VisibleChunk{&chunk_entry.second, chunk_distance_sq(chunk_entry.first)});
|
|
}
|
|
}
|
|
|
|
glBindTexture(GL_TEXTURE_2D, g_atlas_texture);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glDisable(GL_BLEND);
|
|
for (const VisibleChunk &visible : visible_chunks) {
|
|
if (visible.chunk->opaque_list) {
|
|
glCallList(visible.chunk->opaque_list);
|
|
}
|
|
}
|
|
for (const Face &face : g_faces) {
|
|
if (!face.water && !face.cloud) {
|
|
draw_face(face);
|
|
}
|
|
}
|
|
|
|
glDisable(GL_TEXTURE_2D);
|
|
glEnable(GL_BLEND);
|
|
glDepthMask(GL_FALSE);
|
|
std::sort(visible_chunks.begin(), visible_chunks.end(), [](const VisibleChunk &a, const VisibleChunk &b) {
|
|
return a.distance_sq > b.distance_sq;
|
|
});
|
|
for (const VisibleChunk &visible : visible_chunks) {
|
|
if (visible.chunk->transparent_list) {
|
|
glCallList(visible.chunk->transparent_list);
|
|
}
|
|
}
|
|
for (const Face &face : g_faces) {
|
|
if (face.water || face.cloud) {
|
|
draw_face(face);
|
|
}
|
|
}
|
|
glDepthMask(GL_TRUE);
|
|
|
|
draw_crosshair();
|
|
SwapBuffers(g_hdc);
|
|
}
|
|
|
|
void php_craft_render_loading(Int done, Int total) {
|
|
const int d = std::max(0, static_cast<int>(done));
|
|
const int t = std::max(1, static_cast<int>(total));
|
|
const float progress = std::min(1.0f, static_cast<float>(d) / static_cast<float>(t));
|
|
|
|
wchar_t title[128];
|
|
swprintf_s(title, L"\x6b63\x5728\x6e32\x67d3\x4e16\x754c... %d/%d", d, t);
|
|
SetWindowTextW(g_hwnd, title);
|
|
|
|
glViewport(0, 0, g_width, std::max(1, g_height));
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
draw_sky_gradient();
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
glOrtho(-1, 1, -1, 1, -1, 1);
|
|
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix();
|
|
glLoadIdentity();
|
|
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDisable(GL_TEXTURE_2D);
|
|
glDisable(GL_FOG);
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
const float left = -0.56f;
|
|
const float right = 0.56f;
|
|
const float bottom = -0.18f;
|
|
const float top = -0.11f;
|
|
const float fill = left + (right - left) * progress;
|
|
|
|
glColor4f(0.0f, 0.0f, 0.0f, 0.32f);
|
|
glBegin(GL_QUADS);
|
|
glVertex2f(left - 0.02f, bottom - 0.02f);
|
|
glVertex2f(right + 0.02f, bottom - 0.02f);
|
|
glVertex2f(right + 0.02f, top + 0.02f);
|
|
glVertex2f(left - 0.02f, top + 0.02f);
|
|
glEnd();
|
|
|
|
glColor4f(0.92f, 0.96f, 1.0f, 1.0f);
|
|
glBegin(GL_QUADS);
|
|
glVertex2f(left, bottom);
|
|
glVertex2f(fill, bottom);
|
|
glVertex2f(fill, top);
|
|
glVertex2f(left, top);
|
|
glEnd();
|
|
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_FOG);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glEnable(GL_DEPTH_TEST);
|
|
|
|
glPopMatrix();
|
|
glMatrixMode(GL_PROJECTION);
|
|
glPopMatrix();
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
SwapBuffers(g_hdc);
|
|
}
|
|
|
|
void php_craft_sleep(Int milliseconds) {
|
|
Sleep(static_cast<DWORD>(milliseconds));
|
|
}
|
|
|
|
Bool php_craft_key_pressed(Int key) {
|
|
const int k = static_cast<int>(key);
|
|
return k >= 0 && k < 256 && g_keys[k];
|
|
}
|
|
|
|
Float php_craft_mouse_delta_x() {
|
|
const double value = g_mouse_dx;
|
|
g_mouse_dx = 0.0;
|
|
return value;
|
|
}
|
|
|
|
Float php_craft_mouse_delta_y() {
|
|
const double value = g_mouse_dy;
|
|
g_mouse_dy = 0.0;
|
|
return value;
|
|
}
|
|
|
|
void php_craft_set_camera(Float x, Float y, Float z, Float yaw, Float pitch) {
|
|
g_camera_x = static_cast<float>(x);
|
|
g_camera_y = static_cast<float>(y);
|
|
g_camera_z = static_cast<float>(z);
|
|
g_camera_yaw = static_cast<float>(yaw);
|
|
g_camera_pitch = static_cast<float>(pitch);
|
|
}
|
|
|
|
Float php_craft_get_time() {
|
|
static LARGE_INTEGER freq{};
|
|
static LARGE_INTEGER start{};
|
|
if (freq.QuadPart == 0) {
|
|
QueryPerformanceFrequency(&freq);
|
|
QueryPerformanceCounter(&start);
|
|
}
|
|
LARGE_INTEGER now{};
|
|
QueryPerformanceCounter(&now);
|
|
return static_cast<double>(now.QuadPart - start.QuadPart) / static_cast<double>(freq.QuadPart);
|
|
}
|
|
|
|
Bool php_craft_confirm_exit() {
|
|
disable_mouse_capture();
|
|
const int result = MessageBoxA(
|
|
g_hwnd,
|
|
"Exit this world?",
|
|
"Confirm Exit",
|
|
MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2);
|
|
enable_mouse_capture();
|
|
g_mouse_dx = 0.0;
|
|
g_mouse_dy = 0.0;
|
|
g_mouse_ready = false;
|
|
g_keys[VK_ESCAPE] = false;
|
|
return result == IDYES;
|
|
}
|
|
|