{"id":53,"date":"2025-02-24T08:58:13","date_gmt":"2025-02-24T08:58:13","guid":{"rendered":"https:\/\/thatuglydude.com\/?p=53"},"modified":"2025-02-24T22:42:02","modified_gmt":"2025-02-24T22:42:02","slug":"emerald-world-3","status":"publish","type":"post","link":"https:\/\/thatuglydude.com\/index.php\/2025\/02\/24\/emerald-world-3\/","title":{"rendered":"Emerald World #3"},"content":{"rendered":"\n<p>The original game&#8217;s map &#8220;ground&#8221; consists of textures layered in a 2 dimensional tile map. I could extract the map from the game using MMExtensions, as described in my <a href=\"https:\/\/thatuglydude.com\/index.php\/2025\/02\/16\/emerald-world-2\/\" data-type=\"link\" data-id=\"https:\/\/thatuglydude.com\/index.php\/2025\/02\/16\/emerald-world-2\/\">second post<\/a>, however, the values of the tiles were ranging from 1 to 219. This suggests a total number of tiles of the original game is at least 219, or, some tiles are missing.<\/p>\n\n\n\n<p>For simplicity&#8217;s sake, I decided to prepare a reduced tile map just for the partial remake, hence I had to pack the tiles. To reuse the tile map, I decided to reduce the tile ids to the smallest available ones, with the following relatively simple Python script.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from PIL import Image\nimport numpy\n\n# Load the raw CSV\nmy_data = numpy.genfromtxt(\"emerald_tile_map.txt\", delimiter=\",\")\nprint(my_data)\n\n# Get the values - unique, since we're using a set\nall_values = set()\nfor x in my_data:\n  for y in x:\n    if y not in all_values:\n      all_values.add(y)\n\nsorted_vals = sorted(list(all_values))\nprint(f\"total unique vals: {len(sorted_vals)}\")\n\n# Convert the values to the smallest available\n# Set is sorted, but coming from C++, let's be sure\nconvert_map = {}\nfor i in range(len(sorted_vals)):\n  convert_map&#091;sorted_vals&#091;i]] = i\nprint(convert_map)\n\n# Map the old values into new ones\nnew_data = numpy.copy(my_data)\nfor old, new in convert_map.items():\n  new_data&#091;my_data == old] = new\nprint(new_data)\n\n# Save the image\nfrom PIL import Image\nim = Image.fromarray(new_data)\nim2 = im.convert(\"L\")\nim2.save(\"emerald_tile_map.bmp\")\n\n# Save to CSV\nnumpy.savetxt(\"emerald_tile_map2.csv\", new_data, fmt=\"%d\", delimiter=\",\")<\/code><\/pre>\n\n\n\n<p>The result is a map with tile values updated, for example: 1 -&gt; 0, 174 -&gt; 56, 218 -&gt; 79 (80 values in total, ranging from 0 to 79 inclusive).<\/p>\n\n\n\n<p>With the updated values, I could create a tile map. In my case, that was a 10&#215;10 block of 128&#215;128 tiles, with some missing. For the purpose I used Gimp, with a visible and snappy grid of 128px, and added all the necessary images, and eventually exported it to PNG. It looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-style-default wp-duotone-unset-1\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1024\" src=\"https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/tile_set-1024x1024.png\" alt=\"\" class=\"wp-image-55\" srcset=\"https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/tile_set-1024x1024.png 1024w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/tile_set-300x300.png 300w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/tile_set-150x150.png 150w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/tile_set-768x768.png 768w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/tile_set.png 1280w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>I have noticed some tiles seemed to require duplicating the original image, i.e. 7-14, looking at the map, all looked the same.<\/p>\n\n\n\n<p>The PNG has temporarily added numbers, as those would be shown on the rendered 3d scene, for easier debugging:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large wp-duotone-unset-2\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"597\" src=\"https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/emerald_island_with_textures-1024x597.png\" alt=\"\" class=\"wp-image-56\" srcset=\"https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/emerald_island_with_textures-1024x597.png 1024w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/emerald_island_with_textures-300x175.png 300w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/emerald_island_with_textures-768x448.png 768w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/emerald_island_with_textures-1536x896.png 1536w, https:\/\/thatuglydude.com\/wp-content\/uploads\/2025\/02\/emerald_island_with_textures.png 1929w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>The result above is achieved with a fragment shader. The UV variable is an input in the range of [0,1], telling us what point of the texture is currently being sampled.<\/p>\n\n\n\n<p>The texture we first look into is the tile map &#8211; to pick the id of the tile texture we need.<\/p>\n\n\n\n<p>Given a tile id, we now must convert that to a row and column pair, but simple mod and div operations: we now have a &#8220;cell&#8221; in the 2d tile atlas.<\/p>\n\n\n\n<p>All we need now, is to convert the UV position from meaning [0,1] on the entire map, to just look at a map&#8217;s single tile, and where relative to that we are. This is again normalized to [0,1]. As an example, consider the flow of the values we use.<\/p>\n\n\n\n<p>The map has 128&#215;128 cells. Let&#8217;s start with UV of [1\/128 + 1\/256, 1\/128 + 1\/256].<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>This location on the tile map is the second row, and second column [1,1] (the area that maps to this is for both x and y: [1\/128, 2\/128]; the current point is in the middle of that). Let&#8217;s assume the tile ID ended up being 25.<\/li>\n\n\n\n<li>We are at 50% of the tile from the tile&#8217;s beginning to its end, hence mod_uv is [0.5, 0.5].<\/li>\n\n\n\n<li>We now must move to the tile id position on the atlas of the tile ID 25. Since the atlas in this example is 10&#215;10 tiles, we select the 3rd row (values 20-29) and 6th column (counting from 20, &#8220;from zero&#8221;).<\/li>\n\n\n\n<li>We now just move by the [0.5, 0,5] vector from the start of the tile on the atlas, and voila.<\/li>\n<\/ol>\n\n\n\n<p>More complex write-up than I wish, but here&#8217;s the code!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>shader_type spatial;\n\/\/ TODO understand and pick those:\n\/\/render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;\n\nuniform sampler2D atlas_texture_albedo : source_color,filter_nearest,repeat_enable;\nuniform float atlas_tile_count;\n\nuniform sampler2D tile_map;\nuniform float tile_map_size;\n\nuniform sampler2D height_map;\nuniform float height_ratio = 50.0;\n\nvoid vertex() {\n\tVERTEX.y += texture(height_map, UV).r * height_ratio;\n}\n\nvoid fragment() {\n\tvec2 base_uv = UV;\n\t\n\t\/\/ Get the current tile id\n\tfloat tile_id = texelFetch(tile_map, ivec2(base_uv * vec2(tile_map_size, tile_map_size)), 0).r * 255.0;\n\n\t\/\/ Find the coords of the current tile\n\tfloat atlas_col = mod(tile_id, atlas_tile_count);\n\tfloat atlas_row = (tile_id - atlas_col) \/ atlas_tile_count;\n\t\n\t\/\/ Normalize to &#091;0,1] again, but in terms of a single tile\n\tfloat tile_map_multiplier = 1.0 \/ tile_map_size;\n\tvec2 mod_uv = mod(base_uv, tile_map_multiplier) \/ tile_map_multiplier;\n\n\t\/\/ Get the UV position relative to an atlas tile size\n\tfloat atlas_multiplier = 1.0 \/ atlas_tile_count;\n\tvec2 atlas_uv = vec2(atlas_col, atlas_row) * atlas_multiplier + mod_uv * atlas_multiplier;\n\tALBEDO = texture(atlas_texture_albedo, atlas_uv).rgb;\n}<\/code><\/pre>\n\n\n\n<p>Finally, here&#8217;s a very brief video showcasing the current state.<\/p>\n\n\n\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Emerald World #3: Textured map with proto structures\" width=\"500\" height=\"281\" src=\"https:\/\/www.youtube.com\/embed\/4g88x6qz5gE?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n<\/div><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>The original game&#8217;s map &#8220;ground&#8221; consists of textures layered in a 2 dimensional tile map. I could extract the map from the game using MMExtensions, as described in my second post, however, the values of the tiles were ranging from 1 to 219. This suggests a total number of tiles of the original game is [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,4,5],"tags":[],"class_list":["post-53","post","type-post","status-publish","format-standard","hentry","category-gamedev","category-godot","category-mightandmagic"],"_links":{"self":[{"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/posts\/53","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/comments?post=53"}],"version-history":[{"count":3,"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/posts\/53\/revisions"}],"predecessor-version":[{"id":59,"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/posts\/53\/revisions\/59"}],"wp:attachment":[{"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/media?parent=53"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/categories?post=53"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thatuglydude.com\/index.php\/wp-json\/wp\/v2\/tags?post=53"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}