Skip to content

Commit

Permalink
Procedural layers.
Browse files Browse the repository at this point in the history
  • Loading branch information
iwatkot committed Jan 6, 2025
1 parent 4aafac4 commit 31c7994
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 8 deletions.
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 31c7994

Please sign in to comment.