Skip to content

Commit

Permalink
Files for procedural generation
Browse files Browse the repository at this point in the history
* Procedural layers.

* README update.
  • Loading branch information
iwatkot authored Jan 6, 2025
1 parent 4aafac4 commit eed5851
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ Let's have a closer look at the fields:
- `background` - set it to True for the textures, which should have impact on the Background Terrain, by default it's used to subtract the water depth from the DEM and background terrain.
- `info_layer` - if the layer is saving some data in JSON format, this section will describe it's name in the JSON file. Used to find the needed JSON data, for example for fields it will be `fields` and as a value - list of polygon coordinates.
- `invisible` - set it to True for the textures, which should not be drawn in the files, but only to save the data in the JSON file (related to the previous field).
- `procedural` - is a list of corresponding files, that will be used for a procedural generation. For example: `"procedural": ["PG_meadow", "PG_acres"]` - means that the texture will be used for two procedural generation files: `masks/PG_meadow.png` and `masks/PG_acres.png`. Note, that the one procuderal name can be applied to multiple textures, in this case they will be merged into one mask.

## Background terrain
The tool now supports the generation of the background terrain. If you don't know what it is, here's a brief explanation. The background terrain is the world around the map. It's important to create it because if you don't, the map will look like it's floating in the void. The background terrain is a simple plane that can (and should) be textured to look fine.<br>
Expand Down
Binary file modified data/fs25-map-template.zip
Binary file not shown.
21 changes: 14 additions & 7 deletions data/fs25-texture-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"width": 8,
"color": [70, 70, 70],
"priority": 1,
"info_layer": "roads"
"info_layer": "roads",
"procedural": ["PG_roads"]
},
{
"name": "asphaltGravel",
Expand All @@ -34,7 +35,8 @@
"count": 2,
"tags": { "building": true },
"width": 8,
"color": [130, 130, 130]
"color": [130, 130, 130],
"procedural": ["PG_buildings"]
},
{
"name": "concreteGravelSand",
Expand Down Expand Up @@ -71,7 +73,8 @@
"tags": { "natural": "grassland" },
"color": [34, 255, 34],
"priority": 0,
"usage": "grass"
"usage": "grass",
"procedural": ["PG_grass"]
},
{
"name": "grassClovers",
Expand All @@ -88,7 +91,8 @@
"width": 2,
"color": [11, 66, 0],
"usage": "forest",
"priority": 5
"priority": 5,
"procedural": ["PG_forest"]
},
{
"name": "grassDirtPatchyDry",
Expand Down Expand Up @@ -140,15 +144,17 @@
"tags": { "highway": ["secondary", "tertiary", "road", "service"] },
"width": 4,
"color": [140, 180, 210],
"info_layer": "roads"
"info_layer": "roads",
"procedural": ["PG_roads"]
},
{
"name": "mudDark",
"count": 2,
"tags": { "landuse": ["farmland", "meadow"] },
"color": [47, 107, 85],
"priority": 4,
"info_layer": "fields"
"info_layer": "fields",
"procedural": ["PG_meadow", "PG_acres"]
},
{
"name": "mudDarkGrassPatchy",
Expand Down Expand Up @@ -221,6 +227,7 @@
},
"width": 10,
"color": [255, 20, 20],
"background": true
"background": true,
"procedural": ["PG_water"]
}
]
55 changes: 54 additions & 1 deletion maps4fs/generator/texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import os
import re
import shutil
from collections import defaultdict
from typing import Any, Callable, Generator, Optional

Expand Down Expand Up @@ -69,6 +70,7 @@ def __init__( # pylint: disable=R0917
usage: str | None = None,
background: bool = False,
invisible: bool = False,
procedural: list[str] | None = None,
):
self.name = name
self.count = count
Expand All @@ -81,6 +83,7 @@ def __init__( # pylint: disable=R0917
self.usage = usage
self.background = background
self.invisible = invisible
self.procedural = procedural

def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
"""Returns dictionary with layer data.
Expand All @@ -99,6 +102,7 @@ def to_json(self) -> dict[str, str | list[str] | bool]: # type: ignore
"usage": self.usage,
"background": self.background,
"invisible": self.invisible,
"procedural": self.procedural,
}

data = {k: v for k, v in data.items() if v is not None}
Expand Down Expand Up @@ -212,6 +216,10 @@ def preprocess(self) -> None:

self._weights_dir = self.game.weights_dir_path(self.map_directory)
self.logger.debug("Weights directory: %s.", self._weights_dir)
self.procedural_dir = os.path.join(self._weights_dir, "masks")
os.makedirs(self.procedural_dir, exist_ok=True)
self.logger.debug("Procedural directory: %s.", self.procedural_dir)

self.info_save_path = os.path.join(self.map_directory, "generation_info.json")
self.logger.debug("Generation info save path: %s.", self.info_save_path)

Expand Down Expand Up @@ -251,11 +259,56 @@ def get_layer_by_usage(self, usage: str) -> Layer | None:
return layer
return None

def process(self):
def process(self) -> None:
"""Processes the data to generate textures."""
self._prepare_weights()
self._read_parameters()
self.draw()
self.rotate_textures()
self.copy_procedural()

def copy_procedural(self) -> None:
"""Copies some of the textures to use them as mask for procedural generation.
Creates an empty blockmask if it does not exist."""
blockmask_path = os.path.join(self.procedural_dir, "BLOCKMASK.png")
if not os.path.isfile(blockmask_path):
self.logger.debug("BLOCKMASK.png not found, creating an empty file.")
img = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
cv2.imwrite(blockmask_path, img) # pylint: disable=no-member

pg_layers_by_type = defaultdict(list)
for layer in self.layers:
if layer.procedural:
# Get path to the original file.
texture_path = layer.get_preview_or_path(self._weights_dir)
for procedural_layer_name in layer.procedural:
pg_layers_by_type[procedural_layer_name].append(texture_path)

if not pg_layers_by_type:
self.logger.debug("No procedural layers found.")
return

for procedural_layer_name, texture_paths in pg_layers_by_type.items():
procedural_save_path = os.path.join(self.procedural_dir, f"{procedural_layer_name}.png")
if len(texture_paths) > 1:
# If there are more than one texture, merge them.
merged_texture = np.zeros((self.map_size, self.map_size), dtype=np.uint8)
for texture_path in texture_paths:
# pylint: disable=E1101
texture = cv2.imread(texture_path, cv2.IMREAD_UNCHANGED)
merged_texture[texture == 255] = 255
cv2.imwrite(procedural_save_path, merged_texture) # pylint: disable=no-member
self.logger.debug(
"Procedural file %s merged from %s textures.",
procedural_save_path,
len(texture_paths),
)
elif len(texture_paths) == 1:
# Otherwise, copy the texture.
shutil.copyfile(texture_paths[0], procedural_save_path)
self.logger.debug(
"Procedural file %s copied from %s.", procedural_save_path, texture_paths[0]
)

def rotate_textures(self) -> None:
"""Rotates textures of the layers which have tags."""
Expand Down

0 comments on commit eed5851

Please sign in to comment.