mirror of
https://github.com/retroplasma/earth-reverse-engineering.git
synced 2025-12-08 18:02:00 +00:00
850 lines
25 KiB
C++
850 lines
25 KiB
C++
#include <math.h>
|
|
#include <fstream>
|
|
#include <sys/stat.h>
|
|
#ifdef _WIN32
|
|
#include <direct.h>
|
|
#endif
|
|
#include <SDL.h>
|
|
#if defined(_WIN32) || defined(__linux__)
|
|
#include <glad/glad.h>
|
|
#include "gl2/src/glad.c"
|
|
#else
|
|
#define GL_GLEXT_PROTOTYPES
|
|
#endif
|
|
#include <SDL_opengl.h>
|
|
|
|
#define HTTP_IMPLEMENTATION
|
|
#include "http.h"
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include "stb_image.h"
|
|
|
|
#include "proto/rocktree.pb.h"
|
|
#include "proto/rocktree.pb.cc"
|
|
using namespace geo_globetrotter_proto_rocktree;
|
|
|
|
#include "imgui.h"
|
|
#include "examples/imgui_impl_sdl.h"
|
|
#include "examples/imgui_impl_opengl2.h"
|
|
#include "imgui.cpp"
|
|
#include "imgui_widgets.cpp"
|
|
#include "imgui_draw.cpp"
|
|
#include "examples/imgui_impl_sdl.cpp"
|
|
#include "examples/imgui_impl_opengl2.cpp"
|
|
|
|
#include "vector_math.h"
|
|
#include "util.cpp"
|
|
|
|
SDL_Window* sdl_window;
|
|
|
|
const int MAX_LEVEL = 20;
|
|
|
|
typedef struct {
|
|
double n, s, w, e;
|
|
} llbounds_t;
|
|
|
|
void latLonToOctant(double lat, double lon, char octant[MAX_LEVEL]) {
|
|
octant[0] = 0;
|
|
octant[1] = 0;
|
|
llbounds_t box;
|
|
|
|
if (lat < 0.0) { octant[1] |= 2; box.n = 0.0; box.s = -90.0;}
|
|
else { octant[0] |= 2; box.n = 90.0; box.s = 0.0; }
|
|
|
|
if (lon < -90.0) { box.w = -180.0; box.e = -90.0; }
|
|
else if (lon < 0.0) { octant[1] |= 1; box.w = -90.0; box.e = 0.0; }
|
|
else if (lon < 90.0) { octant[0] |= 1; box.w = 0.0; box.e = 90.0; }
|
|
else { octant[0] |= 1; octant[1] |= 1; box.w = 90.0; box.e = 180.0; }
|
|
|
|
int level = MAX_LEVEL;
|
|
for (int i = 2; i < level; i++) {
|
|
octant[i] = 0;
|
|
|
|
double mid_lat = (box.n + box.s) / 2.0;
|
|
double mid_lon = (box.w + box.e) / 2.0;
|
|
|
|
if (lat < mid_lat) {
|
|
box.n = mid_lat;
|
|
} else {
|
|
box.s = mid_lat;
|
|
octant[i] |= 2;
|
|
}
|
|
|
|
if (lon < mid_lon) {
|
|
box.e = mid_lon;
|
|
} else {
|
|
box.w = mid_lon;
|
|
octant[i] |= 1;
|
|
}
|
|
}
|
|
|
|
// to ascii
|
|
for (int i = 0; i < level; i++) octant[i] += '0';
|
|
}
|
|
|
|
void getPathAndFlags(int path_id, char path[], int* level, int* flags) {
|
|
*level = 1 + (path_id & 3);
|
|
path_id >>= 2;
|
|
for (int i = 0; i < *level; i++) {
|
|
path[i] = '0' + (path_id & 7);
|
|
path_id >>= 3;
|
|
}
|
|
*flags = path_id;
|
|
}
|
|
|
|
PlanetoidMetadata* getPlanetoid() {
|
|
unsigned char* data; size_t len;
|
|
if (!fetchData("PlanetoidMetadata", &data, &len)) return NULL;
|
|
|
|
PlanetoidMetadata* planetoid = new PlanetoidMetadata();
|
|
if (planetoid->ParseFromArray(data, len)) {
|
|
free(data);
|
|
return planetoid;
|
|
}
|
|
free(data);
|
|
delete planetoid;
|
|
return NULL;
|
|
}
|
|
|
|
BulkMetadata* getBulk(const char* path, int epoch) {
|
|
char path_buf[200];
|
|
sprintf(path_buf, "cache/bulk_%s_%d.bin", path, epoch);
|
|
unsigned char* data = NULL; size_t len;
|
|
if (!readFile(path_buf, &data, &len)) {
|
|
char url_buf[200];
|
|
sprintf(url_buf, "BulkMetadata/pb=!1m2!1s%s!2u%d", path, epoch);
|
|
if (!fetchData(url_buf, &data, &len)) return NULL;
|
|
writeFile(path_buf, data, len);
|
|
}
|
|
|
|
BulkMetadata* bulk = new BulkMetadata();
|
|
if (bulk->ParseFromArray(data, len)) {
|
|
free(data);
|
|
return bulk;
|
|
}
|
|
free(data);
|
|
delete bulk;
|
|
return NULL;
|
|
}
|
|
|
|
NodeData* getNode(const char* path, int epoch, int texture_format, int imagery_epoch) {
|
|
char path_buf[200];
|
|
sprintf(path_buf, "cache/node_%s_%d_%d_%d.bin",
|
|
path, epoch, texture_format, imagery_epoch);
|
|
unsigned char* data; size_t len;
|
|
if (!readFile(path_buf, &data, &len)) {
|
|
char url_buf[200];
|
|
if (imagery_epoch == -1) { // none
|
|
sprintf(url_buf, "NodeData/pb=!1m2!1s%s!2u%d!2e%d!4b0",
|
|
path, epoch, texture_format);
|
|
} else {
|
|
sprintf(url_buf, "NodeData/pb=!1m2!1s%s!2u%d!2e%d!3u%d!4b0",
|
|
path, epoch, texture_format, imagery_epoch);
|
|
}
|
|
if (!fetchData(url_buf, &data, &len)) return NULL;
|
|
writeFile(path_buf, data, len);
|
|
}
|
|
|
|
NodeData* node = new NodeData();
|
|
if (node->ParseFromArray(data, len)) {
|
|
free(data);
|
|
return node;
|
|
}
|
|
free(data);
|
|
delete node;
|
|
return NULL;
|
|
}
|
|
|
|
struct OrientedBoundingBox {
|
|
vec3_t center;
|
|
vec3_t extents;
|
|
mat3_t orientation;
|
|
};
|
|
|
|
struct PlanetMesh {
|
|
mat4_t transform;
|
|
GLuint vertices_buffer;
|
|
GLuint indices_buffer;
|
|
int element_count;
|
|
GLuint texture;
|
|
float uv_offset[2];
|
|
float uv_scale[2];
|
|
|
|
OrientedBoundingBox obb;
|
|
double lla_min[3]; // longitude, latitude, altitude
|
|
double lla_max[3];
|
|
int level;
|
|
} planet_meshes[128];
|
|
int planet_mesh_count = 0;
|
|
float planet_radius;
|
|
|
|
int unpackVarInt(std::string packed, int* index) {
|
|
int c = 0, d = 1;
|
|
int e;
|
|
do {
|
|
e = (unsigned char)packed[(*index)++];
|
|
c += (e & 0x7F) * d;
|
|
d <<= 7;
|
|
} while (e & 0x80);
|
|
return c;
|
|
}
|
|
|
|
short unpackShort(std::string packed, int* index) {
|
|
auto result = *(short*)(packed.data() + *index);
|
|
*index += 2;
|
|
return result;
|
|
}
|
|
|
|
unsigned short unpackUnsignedShort(std::string packed, int* index) {
|
|
auto result = *(unsigned short*)(packed.data() + *index);
|
|
*index += 2;
|
|
return result;
|
|
}
|
|
|
|
// from decode-resource.js:407
|
|
void unpackObb(std::string data, vec3_t head_node_center, float meters_per_texel, OrientedBoundingBox* obb) {
|
|
int i = 0;
|
|
obb->center[0] = unpackShort(data, &i) * meters_per_texel + head_node_center[0];
|
|
obb->center[1] = unpackShort(data, &i) * meters_per_texel + head_node_center[1];
|
|
obb->center[2] = unpackShort(data, &i) * meters_per_texel + head_node_center[2];
|
|
obb->extents[0] = (unsigned)data[i++] * meters_per_texel;
|
|
obb->extents[1] = (unsigned)data[i++] * meters_per_texel;
|
|
obb->extents[2] = (unsigned)data[i++] * meters_per_texel;
|
|
vec3_t euler;
|
|
euler[0] = unpackUnsignedShort(data, &i) * M_PI / 32768.0f;
|
|
euler[1] = unpackUnsignedShort(data, &i) * M_PI / 65536.0f;
|
|
euler[2] = unpackUnsignedShort(data, &i) * M_PI / 32768.0f;
|
|
float c0 = cosf(euler[0]);
|
|
float s0 = sinf(euler[0]);
|
|
float c1 = cosf(euler[1]);
|
|
float s1 = sinf(euler[1]);
|
|
float c2 = cosf(euler[2]);
|
|
float s2 = sinf(euler[2]);
|
|
obb->orientation[0] = c0 * c2 - c1 * s0 * s2;
|
|
obb->orientation[1] = c1 * c0 * s2 + c2 * s0;
|
|
obb->orientation[2] = s2 * s1;
|
|
obb->orientation[3] = -c0 * s2 - c2 * c1 * s0;
|
|
obb->orientation[4] = c0 * c1 * c2 - s0 * s2;
|
|
obb->orientation[5] = c2 * s1;
|
|
obb->orientation[6] = s1 * s0;
|
|
obb->orientation[7] = -c0 * s1;
|
|
obb->orientation[8] = c1;
|
|
}
|
|
|
|
// from minified js (only positions)
|
|
int unpackVertices(std::string packed, uint8_t** vertices) {
|
|
auto input = (uint8_t*)packed.data();
|
|
auto count = packed.size() / 3;
|
|
*vertices = new uint8_t[8 * count];
|
|
for (auto axis = 0; axis < 3; axis++) {
|
|
uint8_t cycle = 0;
|
|
for (auto j = 0; j < count; j++) {
|
|
(*vertices)[8 * j + axis] = cycle += *(input++);
|
|
}
|
|
}
|
|
return 8 * count;
|
|
}
|
|
|
|
// from minified js
|
|
void unpackTexCoords(std::string packed, uint8_t* vertices, int vertices_len) {
|
|
auto data = (uint8_t*)packed.data();
|
|
auto count = vertices_len / 8;
|
|
auto u_mod = 1 + *(uint16_t *)(data + 0);
|
|
auto v_mod = 1 + *(uint16_t *)(data + 2);
|
|
data += 4;
|
|
auto u_cycle = 0, v_cycle = 0;
|
|
for (auto j = 0; j < count; j++, vertices += 8, data++) {
|
|
*(uint16_t*)(vertices + 4) = u_cycle = (u_cycle + data[0 * count] + (data[2 * count] << 8)) % u_mod;
|
|
*(uint16_t*)(vertices + 6) = v_cycle = (v_cycle + data[1 * count] + (data[3 * count] << 8)) % v_mod;
|
|
}
|
|
}
|
|
|
|
// from minified js
|
|
int unpackIndices(std::string packed, uint16_t** indices) {
|
|
auto offset = 0;
|
|
|
|
// packed -> triangle strip
|
|
auto triangle_strip_len = unpackVarInt(packed, &offset);
|
|
auto triangle_strip = new uint16_t[triangle_strip_len];
|
|
auto num_non_degenerate_triangles = 0;
|
|
for (int zeros = 0, a, b = 0, c = 0, i = 0; i < triangle_strip_len; i++) {
|
|
int val = unpackVarInt(packed, &offset);
|
|
triangle_strip[i] = a = b, b = c, c = zeros - val;
|
|
if (a != b && a != c && b != c) num_non_degenerate_triangles++;
|
|
if (0 == val) zeros++;
|
|
}
|
|
|
|
// triangle strip -> triangles
|
|
auto triangles_len = 3 * num_non_degenerate_triangles;
|
|
auto triangles = new uint16_t[triangles_len];
|
|
for (int i = 0, j = 0; i < triangle_strip_len - 2; i++) {
|
|
int a = triangle_strip[i + 0];
|
|
int b = triangle_strip[i + 1];
|
|
int c = triangle_strip[i + 2];
|
|
if (a == b || a == c || b == c) continue;
|
|
if (i & 1) { // reverse winding
|
|
triangles[j++] = a;
|
|
triangles[j++] = c;
|
|
triangles[j++] = b;
|
|
} else {
|
|
triangles[j++] = a;
|
|
triangles[j++] = b;
|
|
triangles[j++] = c;
|
|
}
|
|
}
|
|
|
|
*indices = triangles;
|
|
delete [] triangle_strip;
|
|
return triangles_len;
|
|
}
|
|
|
|
void loadPlanet() {
|
|
PlanetoidMetadata* planetoid = getPlanetoid();
|
|
printf("earth radius: %f\n", planetoid->radius());
|
|
planet_radius = planetoid->radius();
|
|
|
|
int root_epoch = planetoid->root_node_metadata().epoch();
|
|
BulkMetadata* root_bulk = getBulk("", root_epoch);
|
|
|
|
vec3_t head_node_center;
|
|
for (int i = 0; i < 3; i++) head_node_center[i] = root_bulk->head_node_center()[i];
|
|
|
|
printf("%d metas\n", root_bulk->node_metadata().size());
|
|
for (auto node_meta : root_bulk->node_metadata()) {
|
|
char path[MAX_LEVEL+1];
|
|
int level, flags;
|
|
getPathAndFlags(node_meta.path_and_flags(), path, &level, &flags);
|
|
path[level] = '\0';
|
|
float meters_per_texel = root_bulk->meters_per_texel(level - 1);
|
|
|
|
if (node_meta.meters_per_texel() != 0) meters_per_texel = node_meta.meters_per_texel();
|
|
|
|
if (!(flags & NodeMetadata_Flags_NODATA)) {
|
|
int available_texture_formats = node_meta.has_available_texture_formats() ?
|
|
node_meta.available_texture_formats() :
|
|
root_bulk->default_available_texture_formats();
|
|
|
|
static int supported_texture_formats[] = { // ordered by preference
|
|
Texture_Format_CRN_DXT1,
|
|
Texture_Format_DXT1,
|
|
Texture_Format_JPG,
|
|
};
|
|
|
|
int texture_format = supported_texture_formats[0];
|
|
for (int supported_texture_format : supported_texture_formats) {
|
|
if (available_texture_formats & (1 << (supported_texture_format - 1))) {
|
|
texture_format = supported_texture_format;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int imagery_epoch = -1; // do not use imagery epoch
|
|
if (flags & NodeMetadata_Flags_USE_IMAGERY_EPOCH) {
|
|
imagery_epoch = node_meta.has_imagery_epoch() ?
|
|
node_meta.imagery_epoch() :
|
|
root_bulk->default_imagery_epoch();
|
|
}
|
|
|
|
assert(node_meta.has_epoch());
|
|
NodeData* node = getNode(path, node_meta.epoch(), texture_format, imagery_epoch);
|
|
if (node) {
|
|
PlanetMesh* planet_mesh = &planet_meshes[planet_mesh_count++];
|
|
|
|
planet_mesh->level = level;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
planet_mesh->lla_min[i] = node->kml_bounding_box()[i + 0];
|
|
planet_mesh->lla_max[i] = node->kml_bounding_box()[i + 3];
|
|
}
|
|
|
|
printf("node %.*s %.2f to %.2f, %.2f to %.2f\n", level, path,
|
|
planet_mesh->lla_min[0], planet_mesh->lla_max[0],
|
|
planet_mesh->lla_min[1], planet_mesh->lla_max[1]);
|
|
|
|
unpackObb(node_meta.oriented_bounding_box(), head_node_center,
|
|
meters_per_texel, &planet_mesh->obb);
|
|
|
|
for (int i = 0; i < 16; i++) planet_mesh->transform[i] = (float)node->matrix_globe_from_mesh(i);
|
|
|
|
for (auto mesh : node->meshes()) {
|
|
unsigned short* indices;
|
|
unsigned char* vertices;
|
|
int indices_len = unpackIndices(mesh.indices(), &indices);
|
|
int vertices_len = unpackVertices(mesh.vertices(), &vertices);
|
|
unpackTexCoords(mesh.texture_coordinates(), vertices, vertices_len);
|
|
|
|
planet_mesh->uv_offset[0] = mesh.uv_offset_and_scale(0);
|
|
planet_mesh->uv_offset[1] = mesh.uv_offset_and_scale(1);
|
|
planet_mesh->uv_scale[0] = mesh.uv_offset_and_scale(2);
|
|
planet_mesh->uv_scale[1] = mesh.uv_offset_and_scale(3);
|
|
|
|
glGenBuffers(1, &planet_mesh->vertices_buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, planet_mesh->vertices_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, vertices_len * sizeof(unsigned char), vertices, GL_STATIC_DRAW);
|
|
glGenBuffers(1, &planet_mesh->indices_buffer);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, planet_mesh->indices_buffer);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_len * sizeof(unsigned short), indices, GL_STATIC_DRAW);
|
|
planet_mesh->element_count = indices_len;
|
|
|
|
delete [] indices;
|
|
delete [] vertices;
|
|
|
|
glGenTextures(1, &planet_mesh->texture);
|
|
glBindTexture(GL_TEXTURE_2D, planet_mesh->texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
assert(mesh.texture().size() == 1);
|
|
auto tex = mesh.texture()[0];
|
|
if (tex.format() == Texture_Format_JPG) {
|
|
assert(tex.data().size() == 1); // else we have to concatenate
|
|
std::string data = tex.data()[0];
|
|
int width, height, comp;
|
|
unsigned char* pixels = stbi_load_from_memory((unsigned char*)&data[0],
|
|
data.size(), &width, &height, &comp, 0);
|
|
if (pixels != NULL) {
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
|
|
unsigned char* pixels2 = new unsigned char[width * height];
|
|
unsigned char* pixels_in = pixels;
|
|
unsigned char* pixels_out = pixels2;
|
|
for (int mipmap_level = 1; width > 1 && height > 1; mipmap_level++) {
|
|
if (width > 1 && height > 1) {
|
|
imageHalve(pixels_in, width, height, comp, pixels_out); width /= 2; height /= 2;
|
|
} else if (width > 1) {
|
|
imageHalveHorizontally(pixels_in, width, height, comp, pixels_out); width /= 2;
|
|
} else if (height > 1) {
|
|
imageHalveVertically(pixels_in, width, height, comp, pixels_out); height /= 2;
|
|
}
|
|
glTexImage2D(GL_TEXTURE_2D, mipmap_level, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels_out);
|
|
unsigned char* tmp = pixels_in; pixels_in = pixels_out; pixels_out = tmp; // swap
|
|
}
|
|
delete [] pixels2;
|
|
stbi_image_free(pixels);
|
|
}
|
|
} else assert(false); // TODO: other formats
|
|
}
|
|
delete node;
|
|
|
|
if (planet_mesh_count >= 128) return; // stop after a couple of nodes for now
|
|
} else { // node == NULL
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
// next level
|
|
if (level == 4 && !(flags & NodeMetadata_Flags_LEAF)) { // bulk
|
|
//return; // don't
|
|
BulkMetadata* bulk = getBulk(path, root_epoch);
|
|
if (bulk != NULL) {
|
|
printf("metas %d\n", bulk->node_metadata().size());
|
|
for (auto meta2 : bulk->node_metadata()) {
|
|
getPathAndFlags(meta2.path_and_flags(), path, &level, &flags);
|
|
}
|
|
delete bulk;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
GLuint makeShader(const char* vert_src, const char* frag_src) {
|
|
GLuint vert_shader = glCreateShader(GL_VERTEX_SHADER);
|
|
glShaderSource(vert_shader, 1, &vert_src, NULL);
|
|
glCompileShader(vert_shader);
|
|
GLuint frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
|
glShaderSource(frag_shader, 1, &frag_src, NULL);
|
|
glCompileShader(frag_shader);
|
|
GLuint program = glCreateProgram();
|
|
glAttachShader(program, vert_shader);
|
|
glAttachShader(program, frag_shader);
|
|
glLinkProgram(program);
|
|
glDetachShader(program, vert_shader);
|
|
glDetachShader(program, frag_shader);
|
|
glDeleteShader(vert_shader);
|
|
glDeleteShader(frag_shader);
|
|
return program;
|
|
}
|
|
|
|
GLuint program;
|
|
GLint transform_loc;
|
|
GLint uv_offset_loc;
|
|
GLint uv_scale_loc;
|
|
GLint texture_loc;
|
|
GLint position_loc;
|
|
GLint texcoords_loc;
|
|
void initGL() {
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_CULL_FACE);
|
|
|
|
program = makeShader(
|
|
"uniform mat4 transform;"
|
|
"uniform vec2 uv_offset;"
|
|
"uniform vec2 uv_scale;"
|
|
"attribute vec3 position;"
|
|
"attribute vec2 texcoords;"
|
|
"varying vec2 v_texcoords;"
|
|
"void main() {"
|
|
" v_texcoords = (texcoords + uv_offset) * uv_scale;"
|
|
" gl_Position = transform * vec4(position, 1.0);"
|
|
"}",
|
|
|
|
"#ifdef GL_ES\n"
|
|
"precision mediump float;\n"
|
|
"#endif\n"
|
|
"uniform sampler2D texture;"
|
|
"varying vec2 v_texcoords;"
|
|
"void main() {"
|
|
" gl_FragColor = vec4(texture2D(texture, v_texcoords).rgb, 1.0);"
|
|
"}"
|
|
);
|
|
glUseProgram(program);
|
|
transform_loc = glGetUniformLocation(program, "transform");
|
|
uv_offset_loc = glGetUniformLocation(program, "uv_offset");
|
|
uv_scale_loc = glGetUniformLocation(program, "uv_scale");
|
|
texture_loc = glGetUniformLocation(program, "texture");
|
|
position_loc = glGetAttribLocation(program, "position");
|
|
texcoords_loc = glGetAttribLocation(program, "texcoords");
|
|
}
|
|
|
|
mat4_t cam_rot = {
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
1.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 1.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f,
|
|
};
|
|
float cam_lat = 0.0f;
|
|
float cam_lon = 0.0f;
|
|
float cam_zoom = 0.0f;
|
|
int mouse_x = 0, mouse_y = 0;
|
|
int prev_mouse_x, prev_mouse_y;
|
|
void drawCube();
|
|
void drawPlanet() {
|
|
mat4_t temp0, temp1, temp2,
|
|
translation, rotation, scale,
|
|
model, view, modelview, projection, transform;
|
|
|
|
int width, height;
|
|
SDL_GL_GetDrawableSize(sdl_window, &width, &height);
|
|
glViewport(0, 0, width, height);
|
|
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
float aspect_ratio = (float)width / (float)height;
|
|
float fov = 0.25f * (float)M_PI;
|
|
float right_plane, top_plane, near_plane = 0.01f;
|
|
if (aspect_ratio > 1.0f) {
|
|
right_plane = tanf(0.5f * fov) * near_plane;
|
|
top_plane = right_plane / aspect_ratio;
|
|
} else {
|
|
top_plane = tanf(0.5f * fov) * near_plane;
|
|
right_plane = aspect_ratio * top_plane;
|
|
}
|
|
MatrixFrustum(-right_plane, right_plane, -top_plane, top_plane, near_plane, 10.0f, projection);
|
|
|
|
vec3_t t = { 0.0f, 0.0f, 0.0f };
|
|
t[2] = -(1.0f + near_plane + powf(1.337f, 5.0f - cam_zoom));
|
|
|
|
prev_mouse_x = mouse_x; prev_mouse_y = mouse_y;
|
|
unsigned mouse_buttons = SDL_GetMouseState(&mouse_x, &mouse_y);
|
|
SDL_GetWindowSize(sdl_window, &width, &height);
|
|
|
|
if (mouse_buttons & SDL_BUTTON(SDL_BUTTON_LEFT) &&
|
|
(mouse_x != prev_mouse_x || mouse_y != prev_mouse_y)) {
|
|
vec3_t w0, w1;
|
|
w0[0] = (2.0f * prev_mouse_x / width - 1.0f) * right_plane / near_plane;
|
|
w0[1] = (1.0f - 2.0f * prev_mouse_y / height) * top_plane / near_plane;
|
|
w0[2] = -1.0f;
|
|
VectorNormalize(w0);
|
|
w1[0] = (2.0f * mouse_x / width - 1.0f) * right_plane / near_plane;
|
|
w1[1] = (1.0f - 2.0f * mouse_y / height) * top_plane / near_plane;
|
|
w1[2] = -1.0f;
|
|
VectorNormalize(w1);
|
|
vec3_t origin = { 0.0f, 0.0f, 0.0f };
|
|
float t0, t1;
|
|
if (intersectRaySphere(origin, w0, t, 1.0f, &t0) &&
|
|
intersectRaySphere(origin, w1, t, 1.0f, &t1)) {
|
|
VectorScale(w0, t0, w0);
|
|
VectorSubtract(w0, t, w0);
|
|
VectorScale(w1, t1, w1);
|
|
VectorSubtract(w1, t, w1);
|
|
|
|
vec3_t axis;
|
|
CrossProduct(w0, w1, axis);
|
|
VectorNormalize(axis);
|
|
float dot = DotProduct(w0, w1);
|
|
if (dot > 1.0f) dot = 1.0f;
|
|
float angle = acosf(dot);
|
|
MatrixRotation(axis, angle, temp0);
|
|
MatrixCopy(cam_rot, temp1);
|
|
MatrixMultiply(temp0, temp1, cam_rot);
|
|
}
|
|
}
|
|
|
|
ImGui::Text("%.2f %.2f %.2f", cam_rot[0], cam_rot[4], cam_rot[8]);
|
|
ImGui::Text("%.2f %.2f %.2f", cam_rot[1], cam_rot[5], cam_rot[9]);
|
|
ImGui::Text("%.2f %.2f %.2f", cam_rot[2], cam_rot[6], cam_rot[10]);
|
|
vec3_t pos = { 0.0f, 0.0f, 1.0f }, out;
|
|
mat4_t inv_cam_rot;
|
|
MatrixCopy(cam_rot, inv_cam_rot);
|
|
MatrixTranspose(inv_cam_rot);
|
|
MatrixMultiplyPosition(inv_cam_rot, pos, out);
|
|
cam_lat = asinf(out[2]);
|
|
cam_lon = atan2f(out[1], out[0]);
|
|
ImGui::Text("%.2f %.2f", cam_lat * 180.0f / M_PI, cam_lon * 180.0f / M_PI);
|
|
|
|
float s = 1.0f / planet_radius;
|
|
vec3_t s3 = { s, s, s };
|
|
MatrixScale(s3, scale);
|
|
MatrixTranslation(t, translation);
|
|
MatrixCopy(cam_rot, rotation);
|
|
MatrixMultiply(translation, rotation, temp1);
|
|
|
|
glUseProgram(program);
|
|
glEnableVertexAttribArray(position_loc);
|
|
glEnableVertexAttribArray(texcoords_loc);
|
|
int cam_level = (int)cam_zoom + 2;
|
|
float cam_lon_deg = 180.0f * cam_lon / M_PI;
|
|
float cam_lat_deg = 180.0f * cam_lat / M_PI;
|
|
PlanetMesh* planet_mesh_drawn = NULL;
|
|
for (int mesh_index = 0; mesh_index < planet_mesh_count; mesh_index++) {
|
|
PlanetMesh* planet_mesh = &planet_meshes[mesh_index];
|
|
|
|
if (cam_level != planet_mesh->level) continue;
|
|
//if (cam_lon_deg < planet_mesh->lla_min[0] ||
|
|
// cam_lon_deg > planet_mesh->lla_max[0] ||
|
|
// cam_lat_deg < planet_mesh->lla_min[1] ||
|
|
// cam_lat_deg > planet_mesh->lla_max[1]) continue;
|
|
|
|
MatrixMultiply(scale, planet_mesh->transform, temp0);
|
|
MatrixMultiply(temp1, temp0, modelview);
|
|
MatrixMultiply(projection, modelview, transform);
|
|
glUniformMatrix4fv(transform_loc, 1, GL_FALSE, transform);
|
|
glUniform2fv(uv_offset_loc, 1, planet_mesh->uv_offset);
|
|
glUniform2fv(uv_scale_loc, 1, planet_mesh->uv_scale);
|
|
|
|
glUniform1i(texture_loc, 0);
|
|
glBindTexture(GL_TEXTURE_2D, planet_mesh->texture);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, planet_mesh->vertices_buffer);
|
|
glVertexAttribPointer(position_loc, 3, GL_UNSIGNED_BYTE, GL_FALSE, 8, (void*)0);
|
|
glVertexAttribPointer(texcoords_loc, 2, GL_UNSIGNED_SHORT, GL_FALSE, 8, (void*)4);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, planet_mesh->indices_buffer);
|
|
glDrawElements(GL_TRIANGLES, planet_mesh->element_count, GL_UNSIGNED_SHORT, NULL);
|
|
|
|
planet_mesh_drawn = planet_mesh;
|
|
}
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glDisableVertexAttribArray(position_loc);
|
|
glDisableVertexAttribArray(texcoords_loc);
|
|
glUseProgram(0);
|
|
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadMatrixf(projection);
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadMatrixf(temp1);
|
|
glDisable(GL_DEPTH_TEST);
|
|
glBegin(GL_LINES);
|
|
glColor3f(1.0f, 0.0f, 0.0f);
|
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
|
glVertex3f(1.0f, 0.0f, 0.0f);
|
|
glColor3f(0.0f, 1.0f, 0.0f);
|
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
|
glVertex3f(0.0f, 1.0f, 0.0f);
|
|
glColor3f(0.0f, 0.0f, 1.0f);
|
|
glVertex3f(0.0f, 0.0f, 0.0f);
|
|
glVertex3f(0.0f, 0.0f, 1.0f);
|
|
glEnd();
|
|
glPointSize(5.0f);
|
|
glBegin(GL_POINTS);
|
|
glColor3f(1.0f, 0.0f, 1.0f);
|
|
glVertex3f(out[0], out[1], out[2]);
|
|
glEnd();
|
|
glColor3f(0.0f, 0.0f, 0.0f);
|
|
if (planet_mesh_drawn) { // visualize obb
|
|
MatrixCopy(temp1, view);
|
|
|
|
MatrixCopy(scale, temp0); // save
|
|
MatrixTranslation(planet_mesh_drawn->obb.center, translation);
|
|
MatrixCopy34(planet_mesh_drawn->obb.orientation, rotation);
|
|
MatrixScale(planet_mesh_drawn->obb.extents, scale);
|
|
|
|
MatrixMultiply(rotation, scale, temp2);
|
|
MatrixMultiply(translation, temp2, temp1);
|
|
MatrixMultiply(temp0, temp1, model);
|
|
MatrixMultiply(view, model, modelview);
|
|
|
|
glPushMatrix();
|
|
glLoadMatrixf(modelview);
|
|
drawCube();
|
|
glPopMatrix();
|
|
}
|
|
glEnable(GL_DEPTH_TEST);
|
|
}
|
|
|
|
void drawCube() {
|
|
glBegin(GL_LINES);
|
|
glVertex3f(-1.0f, -1.0f, -1.0f);
|
|
glVertex3f(1.0f, -1.0f, -1.0f);
|
|
glVertex3f(1.0f, -1.0f, -1.0f);
|
|
glVertex3f(1.0f, 1.0f, -1.0f);
|
|
glVertex3f(1.0f, 1.0f, -1.0f);
|
|
glVertex3f(-1.0f, 1.0f, -1.0f);
|
|
glVertex3f(-1.0f, 1.0f, -1.0f);
|
|
glVertex3f(-1.0f, -1.0f, -1.0f);
|
|
|
|
glVertex3f(-1.0f, -1.0f, 1.0f);
|
|
glVertex3f(1.0f, -1.0f, 1.0f);
|
|
glVertex3f(1.0f, -1.0f, 1.0f);
|
|
glVertex3f(1.0f, 1.0f, 1.0f);
|
|
glVertex3f(1.0f, 1.0f, 1.0f);
|
|
glVertex3f(-1.0f, 1.0f, 1.0f);
|
|
glVertex3f(-1.0f, 1.0f, 1.0f);
|
|
glVertex3f(-1.0f, -1.0f, 1.0f);
|
|
|
|
glVertex3f(-1.0f, -1.0f, -1.0f);
|
|
glVertex3f(-1.0f, -1.0f, 1.0f);
|
|
glVertex3f(1.0f, -1.0f, -1.0f);
|
|
glVertex3f(1.0f, -1.0f, 1.0f);
|
|
glVertex3f(1.0f, 1.0f, -1.0f);
|
|
glVertex3f(1.0f, 1.0f, 1.0f);
|
|
glVertex3f(-1.0f, 1.0f, -1.0f);
|
|
glVertex3f(-1.0f, 1.0f, 1.0f);
|
|
glEnd();
|
|
}
|
|
|
|
void init() {
|
|
#if 0
|
|
vec3_t x_axis = {1.0f, 0.0f, 0.0f};
|
|
vec3_t y_axis = {0.0f, 1.0f, 0.0f};
|
|
MatrixRotation(x_axis, (float)M_PI * cam_lat / 180.0f, x_rotation);
|
|
MatrixRotation(y_axis, (float)M_PI * cam_lon / 180.0f, y_rotation);
|
|
MatrixMultiply(x_rotation, y_rotation, temp0);
|
|
mat4_t base = {
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
1.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 1.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, 1.0f,
|
|
};
|
|
MatrixMultiply(temp0, base, rotation);
|
|
#endif
|
|
}
|
|
|
|
bool quit = false;
|
|
void mainloop() {
|
|
SDL_Event sdl_event;
|
|
while (SDL_PollEvent(&sdl_event)) {
|
|
switch (sdl_event.type) {
|
|
case SDL_QUIT:
|
|
quit = true;
|
|
break;
|
|
case SDL_KEYDOWN:
|
|
if (sdl_event.key.keysym.sym == SDLK_ESCAPE) quit = true;
|
|
break;
|
|
case SDL_MOUSEWHEEL:
|
|
if (sdl_event.wheel.y > 0) {
|
|
if (cam_zoom < 20.0f) cam_zoom += 1.0f;
|
|
} else if (sdl_event.wheel.y < 0) {
|
|
if (cam_zoom > 0.0f) cam_zoom -= 1.0f;
|
|
}
|
|
break;
|
|
#ifdef EMSCRIPTEN // Touch controls
|
|
case SDL_FINGERDOWN: {
|
|
float x = sdl_event.tfinger.x;
|
|
float y = sdl_event.tfinger.y;
|
|
} break;
|
|
#endif
|
|
}
|
|
ImGui_ImplSDL2_ProcessEvent(&sdl_event);
|
|
}
|
|
|
|
ImGui_ImplOpenGL2_NewFrame();
|
|
ImGui_ImplSDL2_NewFrame(sdl_window);
|
|
ImGui::NewFrame();
|
|
|
|
drawPlanet();
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImGui::Render();
|
|
glUseProgram(0);
|
|
ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData());
|
|
|
|
SDL_GL_SwapWindow(sdl_window);
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
// create cache directory
|
|
createDir("cache");
|
|
|
|
int video_width = 768;
|
|
int video_height = 768;
|
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
|
|
fprintf(stderr, "Couldn't init SDL2: %s\n", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
#ifdef EMSCRIPTEN
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
|
SDL_GL_CONTEXT_PROFILE_ES);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
|
|
#else
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
|
|
SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
|
#endif
|
|
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
|
|
//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
|
|
sdl_window = SDL_CreateWindow("Earth Client",
|
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
|
video_width, video_height,
|
|
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
|
if (!sdl_window) {
|
|
fprintf(stderr, "Couldn't create window: %s\n", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
SDL_GLContext gl_context = SDL_GL_CreateContext(sdl_window);
|
|
if (!gl_context) {
|
|
fprintf(stderr, "Couldn't create OpenGL context: %s\n", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
SDL_GL_SetSwapInterval(1);
|
|
|
|
#if defined(_WIN32) || defined(__linux__)
|
|
// init glad
|
|
if (!gladLoadGL()) {
|
|
fprintf(stderr, "Failed to init glad\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO(); (void)io;
|
|
ImGui::StyleColorsLight();
|
|
|
|
ImGui_ImplSDL2_InitForOpenGL(sdl_window, gl_context);
|
|
ImGui_ImplOpenGL2_Init();
|
|
|
|
initGL();
|
|
loadPlanet();
|
|
init();
|
|
|
|
#ifdef EMSCRIPTEN
|
|
emscripten_set_main_loop(mainloop, 0, 1);
|
|
#else
|
|
while (!quit) mainloop();
|
|
#endif
|
|
|
|
ImGui_ImplOpenGL2_Shutdown();
|
|
ImGui_ImplSDL2_Shutdown();
|
|
ImGui::DestroyContext();
|
|
|
|
SDL_GL_DeleteContext(gl_context);
|
|
SDL_DestroyWindow(sdl_window);
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
} |