Render a minimap of the world - MINALAC-86 - #161
Conversation
0bbeb85 to
3ad62d2
Compare
[Maven Build Status]📑 Commit: 📦 Download artifact: Generator.jar |
3ad62d2 to
d677070
Compare
2d43f7f to
b7506da
Compare
pyrollo
left a comment
There was a problem hiding this comment.
Quelques commentaires déjà
|
Proposition paramétrage: # minetest.yaml
&main-minimap-colors
- *voxel-stone: [122, 74, 57]
---
# full.yaml
# ...
minimaps:
main:
size: 1000
shadows: true
colors: *main-minimap-colors
forEachTile:
# ...
populateMinimap:
type: populateMinimap
minimap: main
after:
- renderWater
- renderForest
- renderGround
- renderBuildings@ignfab/minalac |
|
D'abord une remarque basique sur ce paramétrage : references:
- &main-minimap-colors
- voxel: *voxel-stone
color: [ 122, 74, 57 ]
- voxels: [ *voxel-tree-1, *voxel-tree-2 ]
color: #00ff00
- filter: # notion de "voxel/placeable filter" qui pourra être factorisée avec le only/neverInsteadOf (difficulté possible : dépend du format)
node: wood_slab
ignoreParam1: true
ignoreParam2: true
color: brownBien entendu la partie filter serait ajoutée plus tard, mais ce que je voulais montrer c'était qu'adopter cette structure nous permettrait de le faire, contrairement au mapping 1:1 simple. Ensuite, question simple : Comment sont traités les voxels inconnus (pour lesquels aucune couleur n'a été associée ici) ? Peut-être qu'un paramètre "unknownVoxelColor" pourrait être indiqué sur la minimap (et dont la valeur serait "transparent" par défaut). Après, une interrogation un peu plus délicate (ou pas ?) : On peut paramétrer la taille de la minimap, mais est-ce qu'on pourrait facilement paramétrer sa fenêtre aussi ? C'est-à-dire, la zone qui est représentée sur la minimap. Ça pourrait être à partir d'une taille en voxels autour du centre de la génération, avec un offset optionnel, par exemple. Enfin, une remarque que je me suis fait mais qui pourra être utile plus tard (toutefois je ne pense pas qu'elle pose le moindre soucis au niveau de la conception actuelle, je pense que ça marchera sans problème le jour où on décidera de l'implémenter), c'est de pouvoir "dessiner" en quelque sorte sur la minimap, avec des tâches un peu comme celles qui font le rendu. En gros, importer les polygones des limites administratives, et les rendre sur la minimap, sans pour autant qu'ils apparaissent en jeu. Ou alors ajouter des lignes de hauteur, ou des toponymes, pictogrammes... qui ne sont pas issus de voxels dans le monde, mais ajouté en surplus sur la minimap. |
@pyrollo avait comme idée que lors qu'un voxel ne possède pas de couleur, l'alpha sera a 0. |
L'idée est pas mal, je le note pour plus tard. |
C'est un choix qui me parait pertinent oui, mais qui n'empêche pas pour autant de le rendre paramétrable (et la valeur par défaut de ce paramètre serait "transparent"). Un intérêt que je vois par exemple serait : Je ne dis pas que ça serait particulièrement utile pour nous, mais juste que c'est trivial à implémenter (je pense), et ça offre cette possibilité en plus, sans imposer de paramétrage supplémentaire si on n'en n'a pas besoin |
|
La question de la minimap soulève une question plus large relative à la sémantique des voxels. Un bâtiment pourrait être construit avec un mélange de stone et stonebricks, tandis que le sol aurait de la dirt et de la stone, et ainsi on retrouverait un voxel "stone" qui serait le même, mais dont la sémantique devrait être différente. En soit, le problème ne se poserait que lorsqu'on voudrait faire disparaître les bâtiments mais pas le sol, ou inversement. Dans un premier temps on peut se contenter d'ignorer le problème en disant que l'usage premier des minimaps est de représenter le contenu réel du monde, et on verra plus tard si l'usage évolue |
c4c976f to
fb55c41
Compare
6773373 to
cd9381f
Compare
naulan-chrzaszcz
left a comment
There was a problem hiding this comment.
Des commentaires à moi même
Hmm intéressant, ça veut donc dire que j'ai dû oublier quelque chose. Ou alors ça révèle un problème dans l'implémentation de l'interpolation bilinéaire, mais ça avait l'air plutôt correct la dernière fois que j'avais regardé. Dans tous les cas comme j'ai dit, maintenant que tu as un algorithme qui marche bien, garde-le et on verra ensuite ce qu'on peut/veut faire. |
Je vais ressayer voir si mon code pour l'interpolation bilinéaire n'est pas foireux (au cas où) |
Je pense que la solution doit être simple, mais il y a une différence notable entre ce qu'on fait pour le MNT et ce qui est fait ici. Pour rééchantillonnage du MNT, on parcourt la destination or ici, on parcourt la source. Donc tout est un peu différent. Toutes ces circonvolutions par différents algorithmes ne résoudront pas le problème de base qui est un problème de parcours tout simplement ( = dans quels pixels mon voxel va aterrir). |
En fait oui voilà c'est ça le problème. De mémoire ton interpolation bilinéaire faisait un calcul "hardcodé" pour répartir le voxel entre 4 pixels de la minimap, ce qui marchera forcément en downscaling puisqu'au mieux ton voxel est pile poil sur 1 pixel et les 3 autres ont un poids de 0, et au pire il est entre les 4 donc réparti sur ces 4 avec des poids appropriés. Bon ça c'était la théorie, en pratique gardes ton algorithme actuel (sauf si il détruit les performances, certes), et on verra plus tard si on a envie de revenir là dessus |
A mon avis, ce serait bien mieux de revenir à l'algo bilinéaire qui a l'avantage d'être plus simple. EDIT: ha ! c'est déjà fait :) |
Spoiler : fonctionne pas. (J'ai juste poussé tel quel, je regarde ce que je peux faire de mon côté '-_-) |
La raison est expliquée dans mon message juste au dessus (c'est ce qu'à évoqué Pierre-Yves en parlant de "parcours"). J'ai essayé de formuler une explication un peu compréhensible, essayes de voir si tu arrives à comprendre ce que j'ai raconté, sinon dis-moi je ferais un dessin ça devrait aider |
Okay pour un dessin |
|
Bon pour conclure avec mes comparaison d'algorithme, j'ai perdu du temps. Pour le rendu du derniers algorithme testé, qui sera définitif pour le minimap (pour cette PR en tout cas) : Exemple d'upscaling :
Exemple de downsampling :
Mise a part si il y a des optimisations a faire sur l'algo utilisé, je ne change plus cette partie de la minimap. Pour plus de rendu avec l'algorithme : #161 (comment) |
|
J'ai quelque relecture à faire sur mon code. |
|
Bravo, ça rend super bien et d'après ce que je vois de tes rendus, ça semble être correct pour la gestion du resampling. Bien joué ! Je ferais une relecture tout à l'heure si j'ai le temps (et que tu as fini ta relecture à toi), sinon peut-être dans l'avion demain ça m'occupera x) |
Fais tout de même un test avec un upscaling de malade pour vérifier que ça marche (genre 10x ou 20x) |
|
Et attention, dans tes deux tests, tu utilises des valeurs bien particulières qui peuvent masquer des problèmes (tu fais /2 et *2). Donc utilise des trucs plus tordus, tu risques d'avoir de nouvelles surprises. |
|
Bon j’abandone cette PR pendant un p'tit moment |
C'était dû à quoi ? |
On en fait quoi du coup ? |
| ```yaml | ||
| minimaps: | ||
| # A minimap named 'overworld' using the default settings | ||
| overworld: {} |
There was a problem hiding this comment.
Simple commentaire. Je tatillonne, je ne suis pas fan de l'écriture {}: ça parait pas intuitif de faire ça pour mettre les valeurs par défaut. Et puis, on l'a très peu utilisé.
En tout cas, c'est correct, et il y a pas le choix de l'utiliser si minimap n'a aucun champ obligatoire (Et ça vaudrait pas le coup de rendre un champ obligatoire)
| * | ||
| * @see <a href="https://en.wikipedia.org/wiki/Alpha_compositing">Alpha Compositing</a> | ||
| */ | ||
| private Color alphaComposite(Color previousColor, Color currentColor) { |
There was a problem hiding this comment.
Je suis curieux, ça donne quoi avec une surface d'eau peu profonde, on voit un peu le fond?
There was a problem hiding this comment.
D'ailleurs, l'histoire des pixels bleus d'en haut c'était quoi?
There was a problem hiding this comment.
D'ailleurs, l'histoire des pixels bleus d'en haut c'était quoi?
C'est les bits d'une couleur qui débordent sur le canal voisin.
Quand tu fais la moyenne des couleurs, il peut arriver que ca soit > 255 donc, j'dois secu le resultat entre une intervale (0 ; 255).
There was a problem hiding this comment.
L'eau est au max 4 blocs de profondeur
There was a problem hiding this comment.
La moyenne de valeurs comprises entre X et Y est nécessairement comprise entre X et Y. Est-ce que tu vois ce que je veux dire ?
There was a problem hiding this comment.
Ok, pour l'eau, merci! (J'avais pas suffisament zoomé haha)
There was a problem hiding this comment.
Et oui, pareil, j'arrive pas comprendre comment la moyenne des couleurs peut donner une valeur supérieure à 255 si chaque couleur a une valeur max de 255, il y avait des couleurs qui faisaient plus de 255?
There was a problem hiding this comment.
Et oui, pareil, j'arrive pas comprendre comment la moyenne des couleurs peut donner une valeur supérieure à 255 si chaque couleur a une valeur max de 255, il y avait des couleurs qui faisaient plus de 255?
Je travaille avec des nombres a virgule, donc, ils se peut que quelque fois ca soit > 255.
| double slopeFactor = 1.0 + Math.atan(computeDirectionalSlope(x, y) * shadowIntensity) / Math.PI; | ||
|
|
||
| MinimapCell cell = minimap.get(x, y); | ||
| cell.set( |
There was a problem hiding this comment.
Du coup, je met le yaml demandé ici!
Le NullPointerException est ici. (Testé sur la version cd163ec)
Caused by: java.lang.NullPointerException: Cannot invoke "com.ignfab.minalac.generator.generation.minimaps.MinimapCell.getRed()" because "cell" is null
Le fichier full.yaml :
heightmaps:
ground:
default: 0
water:
default: 0
minimaps:
overworld: {}
forEachTile:
generateWorld:
type: schedule
do:
altitude:
type: sequence
do:
- type: fetchData
modelType: altitude
provider:
type: wmsFloat
url: https://data.geopf.fr/wms-r/wms
layer: RGEALTI-MNT_PYR-ZIP_FXX_LAMB93_WMS
- type: populateHeightmap
models:
type: altitude
heightmap: ground
- type: setSpawn
heightmap:
sum: [ground, 1]
x: 0
y: 0
fetchBuildings:
type: fetchData
modelType: buildings
provider:
type: wfs
url: https://data.geopf.fr/wfs/wfs
features: BDTOPO_V3:batiment
maxFeaturesPerQuery: 500
postProcessing:
- type: copy
metadata: hauteur
to: height
- type: parse
metadata: height
as: decimal
ifMissing: ignore
ifNotParsable: removeMetadata
- type: truncate
metadata: height
method: round
ifMissing: ignore
- type: default
metadata: height
value: 5
as: integer
fetchWater:
type: fetchData
modelType: water
provider:
type: wfs
url: https://data.geopf.fr/wfs/wfs
features: BDTOPO_V3:surface_hydrographique
fetchRoads:
type: fetchData
modelType: road
provider:
type: wfs
url: https://data.geopf.fr/wfs/wfs
features: BDTOPO_V3:troncon_de_route
flattenWater:
after:
- fetchWater
- altitude
type: copyHeightmap
models:
type: water
from:
localMin: ground
range: 9
to: ground
renderGround:
after:
- altitude
- flattenWater
type: renderHeightmap
at: ground
place:
structure:
- put: wool:pink
at: [0, 0, 0]
- put: wool:pink
at: [0, 0, -3..-1]
- put: wool:pink
at: [0, 0, -20..-4]
computeBuildingsAltitude:
after:
- fetchBuildings
- renderGround
type: computeHeightmapStats
models:
type: buildings
heightmap: ground
compute:
minimum: minimum-ground-altitude
maximum: ground-floor-altitude
flattenGroundBuildings:
after:
- computeBuildingsAltitude
type: fillBetweenHeightmapAndMetadata
models:
type: buildings
heightmap: ground
altitudeMetadata: ground-floor-altitude
placeAbove: *voxel-empty
placeBelow: *voxel-building-ground
renderBuildings:
after: flattenGroundBuildings
type: renderBuildings
models:
type: buildings
filter:
metadata: height
greaterThan: 0
roof: *voxel-cobble
wall: *voxel-stone
window: *voxel-glass
forest:
type: sequence
using: renderGround
do:
- type: fetchData
modelType: vegetation
provider:
type: wfs
url: https://data.geopf.fr/wfs/wfs
features: BDTOPO_V3:zone_de_vegetation
- type: renderSurfaces
after: renderGround
models:
type: vegetation
filter:
metadata: nature
in:
- "Bois"
- "Forêt fermée de conifères"
- "Forêt fermée de feuillus"
- "Forêt fermée mixte"
- "Forêt ouverte"
- "Zone arborée"
heightmap: ground
place:
- pattern:
seed: forest-grass
chance: 0.1
place:
structure:
- at: [0, 0, 1]
put: *forest-grass
- *voxel-forest-ground
- pattern:
seed: forest-tree1
chance: 0.01
place: *forest-tree1
- pattern:
seed: tree2
chance: 0.003
place: *forest-tree2
populateWaterPresence:
after: fetchWater
type: copyHeightmap
models:
type: water
from: 1
to: water
computeWaterDepth:
after: populateWaterPresence
type: copyHeightmap
models:
type: water
from:
remap:
manhattan: water
maximumDistance: 12
targetValue: 0
mapping:
0: 0
1: 1
2: 8
3..3: 10
4..6: 11
7..12: 12
to: water
renderWater:
after:
- renderGround
- computeWaterDepth
# Vegetation and water vector data overlap in certain areas. (Because of vegetation representing canopy)
# The dependency and air voxel below should be removed when cropping is implemented on vector data.
- forest
type: renderHeightmap
minimum:
sum:
- ground
- 1
- product: [ -1, water ]
maximum: ground
place:
structure:
- put: *voxel-water
at: [ 0, 0, 0 ]
# This a dirt fix to remove vegetation on water
- put: *voxel-air
at: [ 0, 0, 1..12 ]
path:
after:
- fetchRoads
- renderGround
- forest
type: renderLines
models:
type: road
filter:
metadata: nature
in: [ "Chemin", "Sentier", "Route empierrée" ]
structure:
- put: *path
at: [ 0, -1..1, 0]
- put: *voxel-air
at: [ 0, -1..1, 1..2 ]
renderBridges:
after: path
type: renderLines
models:
type: road
filter:
metadata: nature
in: [ "Bretelle", "Rond-point", "Route à 1 chaussée", "Route à 2 chaussées", "Type autoroutier"]
renderOnlyWhenAbove:
sum:
- ground
- 1
structure:
# Half bridge structure (sectional view)
axes: [ z, y ]
blueprint:
- "▒▒ ▒▒"
- "▒▒▒▒▒▒▒▒▒▒▒▒▒"
with:
"▒": *voxel-stone
zOffset: -1
renderRoadsPass1:
after: renderBridges
type: renderLines
models:
type: road
filter:
metadata: nature
in: [ "Bretelle", "Rond-point", "Route à 1 chaussée", "Route à 2 chaussées", "Type autoroutier"]
structure:
# Road border lines and air to clean eventual voxels (sectional view)
axes: [ z, y ]
blueprint:
- "·········"
- "·········"
- "█·······█"
with:
"█": *road-paint
"·": *voxel-air
renderRoadsPass2:
after: renderRoadsPass1
type: renderLines
models:
type: road
filter:
metadata: nature
in: [ "Bretelle", "Rond-point", "Route à 1 chaussée", "Route à 2 chaussées", "Type autoroutier"]
structure:
# Road central line and tar (view from above)
axes: [ x, y ]
blueprint:
- "░░░█░░░"
- "░░░█░░░"
- "░░░█░░░"
- "░░░░░░░"
- "░░░░░░░"
with:
"█": *road-paint
"░": *road-tar
populateOverworldMinimap:
after: generateWorld
type: populateMinimap
minimap: overworld
colors: *minimap-color-palette
afterAllTiles:
minimaps:
type: sequence
do:
- type: applyShadingMinimap
minimap: overworld
- type: saveMinimap
minimap: overworld
destination: overworld-minimap.png
format: png
Commande lancée :
mvn -Dmaven.test.skip=true clean package && ./generate.sh minetest full ign $HOME/.minetest/worlds/testo







Issue
MINALAC-86
Changes
Creation of a data structure for representing a 3D world as a miniaturized 2D representation.
This data structure is a class named Minimap.
The Minimap class includes a heightmap to enable 2.5D rendering.
It also includes a resampling system allowing both downscaling and upscaling.
The Minimap itself does not perform operations; these are handled by external tasks, which are as follows:
Before explaining the tasks that allow operations on the minimap, I need to introduce a different behavior for tasks. Some tasks are executed during each tile (this has not changed), but now there are also tasks that can be executed anywhere, including outside tiles.
Tasks that are executed during each tile:
populateMinimap: fills a specified minimap with colors. Example configuration:Tasks that are exécutes anywhere:
applyShadingMinimap: applies a 2.5D effect by using the minimap’s heightmap to cast shadows on the terrain.The sun direction and shadow intensity can be adjusted.
saveMinimap: exports the data of a minimap into a file format supported by the JavaImageIOclass.Of course, multiple Minimaps can be created using a
MinimapStore:Minimaps declared in the MinimapStore are accessible throughout the entire generation process.
Feature description
PopulateMinimapTaskCette tâche calcule la couleur finale pour les coordonnées de chaque voxel.
Elle implémente la composition alpha (Alpha Compositing) pour mélanger les voxels semi-transparents (comme l'eau ou le verre) jusqu'à ce que le pixel atteigne une opacité totale.
SaveMinimapTaskExporte la minimap générée dans un fichier au format d'image supporté par la classe Java
ImageIO, à l'emplacement spécifié relativement au dossier du monde.Applique une ombre basé sur la hauteur lors de exportation.
Note
Chaque tâche décrite dispose de son paramétrage dédié.
Minimap&MinimapStoreMinimappermet la projection du monde 3D en une image 2D. Pour gérer de très grands mondes tout en respectant une limitemaxSize, chaque pixel distribue ses données sur les 4 pixels les plus proches en utilisant des poids bilinéaires.Le
MinimapStoreest ajouté àGenerationpour contenir et gérer plusieurs instances de minimaps.Note
La classe Minimap ne dispose pas de paramétrage propre ; ce sont les tâches
PopulateMinimapTasketSaveMinimapTaskqui permettent d'effectuer des opérations sur ses données.VoxelCette interface permet d'identifier via un String (pour l'instant) un Placeable.
Permet d'exposer l'attribut
typedeMCVoxelet deMTVoxel.ColorDeserializer...
Showcase
Montagne de La Meije
Saint-Mandé - IGN
Self-checks
/docsfolder has been updatedexamples/work the same (or have been adapted if subject to changes in this PR)