How to create real-world terrains
First, we need to decide where we get the data (height maps and satellite maps) from. This part took most of the time I worked on this feature. After some extensive searching I chose:
Now, we need to:
- let user select the place he wants in GUI
- download the data
- save them as images
I will not talk about the 3rd point since image saving is an old feature from the editor.
Let user select the place he wants in GUI
I implemented simple Slippy Map in imgui so user can navigate to place he wants to export.
Downloading the data
Terrain Tiles on AWS
To download a heith map from Amazon, we just need to construct the correct web address like this:
- start with
http://s3.amazonaws.com/elevation-tiles-prod/terrarium/
- add zoom level
http://s3.amazonaws.com/elevation-tiles-prod/terrarium/4/
- add x coordinate
http://s3.amazonaws.com/elevation-tiles-prod/terrarium/4/1/
- add y coordinate and extension
http://s3.amazonaws.com/elevation-tiles-prod/terrarium/4/1/2.png
There is a png image at this address. It contains elevation data in meters, split into RGB value:
height_in_meters = (red * 256 + green + blue / 256) - 32768
XYZ coordinates
Zoom parameter is an integer between 0 and 18. Although higher zoom levels might not be available.
Zoom level N
contains 1 << N x 1 << N tiles.
X
and Y
go from 0 to (1 << N) - 1
.
Left edge (X == 0
) is 180 degrees west.
Top edge (Y == 0
) is 85.0511 degrees north.
Google Statis Maps
Similar to Amazon, we just need to construct the correct web address to get an image from Google:
- start with
http://maps.googleapis.com/maps/api/staticmap?center=
- add latitude and longitude
http://maps.googleapis.com/maps/api/staticmap?center=0,0
- add zoom
http://maps.googleapis.com/maps/api/staticmap?center=0,0&zoom=4
- add some other params
http://maps.googleapis.com/maps/api/staticmap?center=0,0&zoom=4&size=256x256&maptype=satellite
- finally add key
http://maps.googleapis.com/maps/api/staticmap?center=0,0&zoom=4&size=256x256&maptype=satellite&key=YOUR_API_KEY
Coordinates
Since Google uses different coordinate system than Amazon, we need to convert from one to another to be able to download matching data. We already know how Amazon’s XYZ data are related to latitude and longitude, so it should not be a problem. Note that latitude and longitude parameters mark center of the tile.
double tilex2long(float x, int z)
{
return x / pow(2.0, z) * 360.0 - 180;
}
double tiley2lat(float y, int z)
{
double n = M_PI - 2.0 * M_PI * y / pow(2.0, z);
return 180.0 / M_PI * atan(0.5 * (exp(n) - exp(-n)));
}
Winsock
Since there is no simple library to do this I decided to use Winsock to download the images.
1. Initialize Winsock
Before we can use socket, we have to initialize Winsock.
#include <WinSock2.h>
#include <Windows.h>
#include <cmath>
#include <cstdlib>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "Ws2_32.lib")
WORD sockVer;
WSADATA wsaData;
sockVer = 2 | (2 << 8);
if (WSAStartup(sockVer, &wsaData) != 0) return error("failed to initialize Winsock");
2. Create socket
SOCKET socket = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket == INVALID_SOCKET) return error("failed to create socket");
3. transform hostname to ip address
const char* host = "s3.amazonaws.com"
SOCKADDR_IN sin;
setMemory(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
hostent* hostname = gethostbyname(host);
if (!hostname) return false;
const char* ip = inet_ntoa(**(in_addr**)hostname->h_addr_list);
sin.sin_addr.s_addr = ip ? ::inet_addr(ip) : INADDR_ANY;
4. connect
if (connect(socket, (LPSOCKADDR)&sin, sizeof(sin)) != 0)
{
closesocket(socket);
return error("failed to connect");
}
5. Cleanup
When we are done with the socket, clean up
closesocket(socket);
and when we are done with the whole application
WSACleanup();
HTTP
The servers communicate in HTTP. First, we have to send HTTP GET request to the server and then we can receive an answer.
static bool writeRawString(SOCKET socket, const char* str)
{
int len = (int)strlen(str);
int send = ::send(socket, str, len, 0);
return send == len;
}
writeRawString(socket, "GET ");
writeRawString(socket, path);
writeRawString(socket, " HTTP/1.1\r\nHost: ");
writeRawString(socket, host);
writeRawString(socket, "\r\nConnection: close\r\n\r\n");
So the request looks like this:
GET /elevation-tiles-prod/terrarium/4/1/2.png HTTP/1.1\r\n
Host: s3.amazonaws.com\r\n
Connection: close\r\n\r\n
After that server should send us its response and we can read it
Array<u8> data(allocator);
data.reserve(16 * 1024);
u8 buf[1024];
while (int r = ::recv(socket, (char*)buf, sizeof(buf), 0))
{
if (r > 0)
{
data.resize(data.size() + r);
copyMemory(&data[data.size() - r], buf, r);
}
}
Note: we should handle errors here, but I am not doing it because I am lazy.
Response from the server should contain HTTP header and png image right after the header.
Get Lumix Engine
Lumix Engine
Open Source 3D Game Engine
Status | In development |
Category | Tool |
Author | mikulas.florek |
Tags | 3D, Game engine, Godot, opensource |
Languages | English |
More posts
- DX version now availableJan 18, 2020
Leave a comment
Log in with itch.io to leave a comment.