Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Files for procedural generation #121

Merged
merged 2 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading