Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mapsSvg

Générateur procédural de cartes SVG en vue isométrique, dans un style line art à l'encre : noir sur blanc, aucun aplat, le volume est porté uniquement par la densité des hachures.

Le dessin est destiné à un traceur (plotter). Cette contrainte gouverne toute l'architecture — voir « Règles de la plume » ci-dessous.

Le projet se construit entité par entité. Chaque sous-entité (sapin, maison, rocher, pont, rivière…) est un générateur autonome et testable ; l'assemblage de la carte complète viendra ensuite, en instanciant ces générateurs et en les triant en profondeur.

Aujourd'hui disponible : le générateur de sapins.

Démarrage

npm install                 # typescript + @types/node (rien d'autre)
npm test                    # garde-fous du générateur
npm run check               # typage

# un sapin
npm run render -- --kind fir --seed 42 --out out/sapin.svg

# une planche de contact : variantes, diversité, balayages de paramètres,
# et une petite forêt isométrique
npm run gallery -- --kind fir --count 24 --out out/gallery.html

# export destiné à la machine : aucun remplissage, aucun fond
npm run render -- --kind fir --seed 42 --plot --out out/sapin-plot.svg

Node 22+ suffit : les fichiers TypeScript sont exécutés directement (--experimental-strip-types), il n'y a pas d'étape de build.

Règles de la plume

  1. Une seule épaisseur de trait dans tout le document. C'est celle de la plume physique. L'attribut stroke-width est posé une seule fois, sur le groupe racine du composant (penAttrs, dans src/core/pen.ts) ; aucun tracé ne porte le sien. Un test le vérifie sur toutes les combinaisons d'options : il doit y avoir exactement un stroke-width par document.
  2. Pour appuyer un trait, on le repasse. strokePasses retrace le contour n fois, chaque passe décalée d'exactement une largeur de plume vers l'intérieur : les passes se touchent, sans recouvrement ni interstice. Voir l'option outlinePasses et le preset contour-appuye.
  3. La valeur ne vient que de l'écartement. Clair ou foncé se règle par l'espacement des hachures, jamais par l'épaisseur ni par un gris. Au-delà d'un certain assombrissement (shade > 0.72), une seconde famille croisée apparaît dans le cœur de l'ombre plutôt que de resserrer jusqu'à l'aplat. safeSpacing empêche deux traits parallèles de se toucher.
  4. Les écartements sont exprimés en largeurs de plume, pas en fraction de l'objet : c'est la plume qui fixe la finesse possible. Une légère dépendance à la taille (en racine carrée) évite qu'un très grand sujet ne vire à la masse noire.
  5. --plot produit la géométrie pure : aucun remplissage, aucun fond.

Le blanc opaque n'est qu'un outil d'occlusion à l'écran. Un traceur ne remplit rien : en mode --plot, les recouvrements entre entités redeviennent visibles. Les résoudre géométriquement (élimination des parties cachées) est un travail à faire au niveau du compositeur de carte, pas des entités.

Ce qui fait un dessin plutôt qu'un tracé

Trois signatures trahissent immédiatement la machine. Le générateur les traite comme des règles, pas comme des réglages :

  1. Aucune symétrie. Les deux flancs sont tirés indépendamment — largeurs, hauteurs de pointes, profondeurs d'encoches diffèrent d'un côté à l'autre. Un arbre miroir se repère au premier coup d'œil ; comparez les variantes standard et symetrique sur la planche de contact. asymmetry: 0 rétablit le miroir si on en veut un.
  2. Aucune arête droite. Chaque descente vers une pointe est creusée vers l'intérieur (ce qui affûte la flèche au lieu de l'arrondir), chaque dessous de branche s'affaisse. Réglé par bow.
  3. L'ombre suit la forme. Ce n'est pas un remplissage calculé sur la silhouette entière mais des traits suspendus sous chaque étage de branches, dans l'axe de la branche, longs près de la pointe et courts près du tronc — la façon dont on ombre réellement un conifère à la plume. Le flanc éclairé n'en reçoit que quelques-uns, juste assez pour que le dessin ne soit pas à moitié vide.

S'y ajoute le geste, dans src/core/ink.ts : la ligne dérive lentement (bruit basse fréquence) au lieu de vibrer, la plume se lève par moments (gaps), et le contour dépasse son point de fermeture au lieu de se refermer pile (overshoot).

Le générateur de sapins

import { createFir } from "./src/entities/fir.ts";

const sapin = createFir({ seed: "foret-01", height: 120 });
// sapin.node       -> groupe SVG en coordonnées locales
// sapin.bbox       -> boîte englobante
// sapin.footprint  -> empreinte au sol projetée

Deux morphologies :

  • silhouette (défaut) — un seul contour fermé. Depuis la flèche, chaque flanc descend vers une pointe de branche, remonte brièvement vers l'intérieur (l'encoche), puis repart plus bas et plus large ; le bas se referme sur un tronc court. C'est la morphologie de l'image de référence, lisible jusqu'à ~20 px de haut.
  • tiers — étages triangulaires empilés, remplis en blanc opaque et se masquant les uns les autres. Plus riche, pour les sujets de premier plan.

Trois textures : hatch (défaut — traits d'ombre suspendus sous les branches en style silhouette, hachures parallèles croisées en style tiers), stipple (points et tirets courts, tous tracés à la largeur de la plume), ou none. Dans tous les cas la matière se pose du côté opposé à la lumière.

Paramètres principaux

Paramètre Défaut Rôle
seed "fir" Tout est déterministe : même seed, même SVG au caractère près.
height 120 Hauteur totale en pixels, du sol à la flèche.
widthRatio 0.5 Largeur du feuillage, en fraction de la hauteur.
tiers auto Nombre d'étages de branches (auto d'après la hauteur).
taper 0.85 Profil du cône : < 1 trapu, > 1 élancé.
notchDepth / notchRise 0.52 / 0.15 Géométrie des encoches entre étages.
droop 0.22 Plongée des pointes de branches.
bow 0.06 Galbe des arêtes ; 0 donne des segments droits.
asymmetry 1 Divergence entre les deux flancs ; 0 = miroir parfait.
shade 0.55 Densité de la texture d'ombre, 0 à 1.
jitter 0.5 Dérive du trait ; 0 donne un tracé géométrique exact.
gaps 0.05 Proportion du contour où la plume se lève.
overshoot 2 Dépassement au point de fermeture, en largeurs de plume.
penWidth 1 Largeur de plume, unique pour tout le composant.
outlinePasses 1 Passes du contour ; 2 = deux traits collés.
variation 0.7 Dispersion des paramètres non fournis (voir ci-dessous).
detail 1 0 silhouette nue, 1 standard, 2 branches internes.
lightAngleDeg -135 Direction de la lumière (haut-gauche par défaut).

La liste complète et commentée est dans FirOptions (src/entities/fir.ts).

variation : de la variété sans effort

Un paramètre non fourni est tiré autour de sa valeur par défaut, de sorte que deux seeds donnent deux arbres de morphologies réellement différentes et pas seulement deux textures différentes. Un paramètre fourni explicitement est respecté à l'identique. variation: 0 fige tout et redonne exactement l'arbre par défaut.

Des variantes prêtes à l'emploi sont exposées dans FIR_PRESETS (jeune, eleve, trapu, fleche, esquisse, net, detaille, contour-appuye, ombre-dense, pointille, nu, symetrique, etages…), utilisables en CLI via --preset.

Architecture

src/
  core/
    rng.ts         PRNG déterministe (mulberry32) + sous-générateurs isolés
    noise.ts       bruit de valeur 1D, cohérent, pour la dérive du trait
    geom.ts        vecteurs, projection isométrique, tests polygonaux
    pen.ts         la plume : épaisseur unique, écartements sûrs, encre
    ink.ts         géométrie exacte -> chemins SVG « à la plume », passes
    hatch.ts       remplissage par traits parallèles (scanline) + demi-plan
    texture.ts     pointillés (points et tirets) à l'intérieur d'un polygone
    svg.ts         mini modèle de document SVG + sérialisation
    component.ts   contrat commun aux entités, placement et tri de scène
  entities/
    fir.ts         le générateur de sapins
    index.ts       registre des entités (utilisé par la CLI)
  cli/
    render.ts      rend une entité en fichier SVG
    gallery.ts     planche de contact HTML
test/
  fir.test.ts      déterminisme, épaisseur unique, asymétrie, export traceur

Le contrat Component

Une entité est dessinée en coordonnées locales, l'origine (0, 0) étant son point de contact avec le sol, l'axe y montant en négatif. Elle ne connaît ni sa position sur la carte ni son ordre de dessin :

interface Component {
  kind: string;        // "fir"
  seed: string;
  node: SvgNode;       // groupe SVG local
  bbox: Rect;
  footprint: Vec2[];   // empreinte au sol projetée, pour l'implantation
  groundRadius: number;// rayon au sol (unités monde), espacement et collisions
  height: number;
  penWidth: number;    // épaisseur unique du tracé
  meta: Record<string, unknown>; // paramètres résolus
}

Le compositeur place l'entité (placeComponent) et trie la scène par profondeur (composeScene) : les objets les plus proches de la caméra sont dessinés en dernier et masquent ceux de derrière grâce au remplissage blanc opaque. La section « Composition isométrique » de la planche de contact montre ce mécanisme sur une forêt.

Conventions de dessin

  • Écran : x vers la droite, y vers le bas (comme SVG).
  • Monde : x, y au sol, z vers le haut.
  • Projection isométrique vraie, axes du sol à 30° : une hauteur z se lit directement en pixels vers le haut. Les entités sont donc dessinées de face, la projection ne servant qu'à les poser sur le terrain.
  • Une seule couleur d'encre, une seule épaisseur, un remplissage blanc opaque pour les occlusions à l'écran uniquement.

Ajouter une entité

  1. Créer src/entities/<nom>.ts exposant create<Nom>(options): Component, construit avec makeComponent — qui pose le stroke-width unique. Ne jamais écrire stroke-width sur un tracé.
  2. L'enregistrer dans src/entities/index.ts (ENTITIES) avec ses presets.
  3. npm run gallery -- --kind <nom> pour juger le style sur une planche.

La CLI et le futur compositeur n'ont alors rien à savoir de plus.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages