Skip to content

Commit 9d25259

Browse files
Ensure turtle scales to the full generation range.
1 parent 2f65663 commit 9d25259

4 files changed

Lines changed: 337 additions & 100 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ Ce projet suit une convention inspirée de Keep a Changelog.
1717
`dom_value_str` / `dom_text`), puis renvoie les sommets ; le JavaScript se réduit
1818
au **shim hôte généré** (`public/js/atelier_lsysteme_shim.js`) plus un chargeur
1919
mince qui trace les sommets sur le canvas (`public/js/renderer-atelier-lsysteme.js`).
20+
- La tortue utilise un **parcours en profondeur sans matérialisation** (symboles
21+
stockés comme codes via `ord()`), supprimant l'ancien plafond de 8000 caractères :
22+
l'aperçu couvre désormais toute la plage de générations (ex. Koch génération 7 =
23+
16 385 sommets) et émet `profondeur` + `angle` par sommet (coloration par segment).
2024
- Les règles **stochastiques** (RNG + alternatives pondérées) restent traitées côté
2125
JavaScript ; le chemin multilingual couvre les grammaires déterministes.
26+
- Le rendu **canvas principal** (renderer.js) reste en JavaScript : y porter le WASM
27+
proprement nécessite de passer axiome/règles en paramètres (les paramètres-chaîne
28+
WAT ne portent pas leur longueur) — bloqué sur les chaînes à longueur préfixée.
2229
- Étape `[4b]` du build (`scripts/compile_wasm.multi`) compilant l'Atelier vers
2330
WASM et émettant le shim hôte généré ; validation de l'export `tracer_lsysteme`.
2431
- Test de parité `tests/atelier_lsysteme_wasm.test.js` vérifiant que l'expansion ET
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"use strict";
2+
3+
/*
4+
* Pont JS minimal pour l'Atelier L-système.
5+
*
6+
* La grammaire L-système (réécriture) ET l'interprétation tortue (géométrie)
7+
* vivent en multilingual (src/atelier_lsysteme.multi → public/atelier_lsysteme.wasm).
8+
* Ce module ne fait que : charger le WASM avec le shim hôte GÉNÉRÉ (WASI + pont
9+
* DOM), appeler tracer_lsysteme(), puis tracer sur le canvas les sommets renvoyés.
10+
*
11+
* tracer_lsysteme(generations, angle_deg) :
12+
* - lit #lsystem-axiom-input / #lsystem-rules-input via le pont DOM ;
13+
* - écrit l'aperçu dans #lsystem-string-preview ;
14+
* - renvoie un pointeur vers une liste : [0] = nombre de sommets np, puis
15+
* np triplets (x, y, déplacer) — déplacer = 1 pour moveTo, 0 pour lineTo.
16+
*
17+
* L'allocateur WASM est de type bump (sans libération) : l'hôte appelle
18+
* __ml_reset() avant chaque invocation pour récupérer le tas.
19+
*/
20+
21+
import {
22+
createWasiImports,
23+
createDomImports,
24+
} from "./atelier_lsysteme_shim.js?v=20260521-atelier-meta";
25+
26+
const WASM_URL = "atelier_lsysteme.wasm?v=20260521-atelier-meta";
27+
const CAPACITE_CHAINE = 8000; // doit refléter CAPACITE dans le .multi
28+
29+
const memoryRef = { current: null };
30+
const exportsRef = { current: null };
31+
let instancePromise = null;
32+
33+
async function instancier() {
34+
const { wasi_snapshot_preview1 } = createWasiImports(memoryRef, () => {});
35+
const { env } = createDomImports(memoryRef, exportsRef);
36+
const importObject = { wasi_snapshot_preview1, env };
37+
38+
let instance;
39+
try {
40+
const res = await WebAssembly.instantiateStreaming(fetch(WASM_URL), importObject);
41+
instance = res.instance;
42+
} catch {
43+
const resp = await fetch(WASM_URL);
44+
if (!resp.ok) throw new Error(`HTTP ${resp.status} pour ${WASM_URL}`);
45+
const bytes = await resp.arrayBuffer();
46+
const res = await WebAssembly.instantiate(bytes, importObject);
47+
instance = res.instance;
48+
}
49+
memoryRef.current = instance.exports.memory;
50+
exportsRef.current = instance.exports;
51+
return instance.exports;
52+
}
53+
54+
/** Charge (une seule fois) le module WASM de l'atelier. */
55+
export function chargerAtelierLSysteme() {
56+
if (!instancePromise) {
57+
instancePromise = instancier().catch((err) => {
58+
instancePromise = null;
59+
throw err;
60+
});
61+
}
62+
return instancePromise;
63+
}
64+
65+
// Disposition de sortie : [0] = np, puis np quintuplets
66+
// (x, y, déplacer, profondeur, angle). Pas de 5 valeurs par sommet.
67+
const PAS_SOMMET = 5;
68+
69+
/**
70+
* Calcule les sommets L-système via WASM (réinitialise le tas au préalable).
71+
* Renvoie { sommets: Float64Array (x,y,move,prof,angle par sommet), count }
72+
* ou null si indisponible. Valide jusqu'au prochain appel (réinit. du tas).
73+
*/
74+
async function calculerSommets(generations, angleDeg) {
75+
const exports = await chargerAtelierLSysteme();
76+
exports.__ml_reset();
77+
const ptr = exports.tracer_lsysteme(Number(generations) || 0, Number(angleDeg) || 0);
78+
const base = Math.trunc(ptr);
79+
const view = new DataView(memoryRef.current.buffer);
80+
const count = view.getFloat64(base + 8, true); // élément [0] = np
81+
const sommets = new Float64Array(Math.max(0, count) * PAS_SOMMET);
82+
for (let k = 0; k < count; k++) {
83+
const o = base + 8 + 8 * (1 + PAS_SOMMET * k);
84+
sommets[PAS_SOMMET * k] = view.getFloat64(o, true);
85+
sommets[PAS_SOMMET * k + 1] = view.getFloat64(o + 8, true);
86+
sommets[PAS_SOMMET * k + 2] = view.getFloat64(o + 16, true);
87+
sommets[PAS_SOMMET * k + 3] = view.getFloat64(o + 24, true);
88+
sommets[PAS_SOMMET * k + 4] = view.getFloat64(o + 32, true);
89+
}
90+
return { sommets, count };
91+
}
92+
93+
/**
94+
* Trace la proposition L-système sur *canvas* à partir des sommets calculés en
95+
* WASM, en mettant à l'échelle pour remplir le canvas. tracer_lsysteme écrit
96+
* aussi l'aperçu texte (#lsystem-string-preview). Renvoie { pointsCount,
97+
* truncated } pour le compte-rendu, ou un objet par défaut en cas d'échec.
98+
*/
99+
export async function dessinerPropositionLSystemeWasm(canvas, generations, angleDeg) {
100+
try {
101+
const { sommets, count } = await calculerSommets(generations, angleDeg);
102+
if (!canvas) return { pointsCount: count, truncated: count >= CAPACITE_CHAINE };
103+
104+
const ctx = canvas.getContext("2d");
105+
const w = canvas.width;
106+
const h = canvas.height;
107+
ctx.clearRect(0, 0, w, h);
108+
ctx.fillStyle = "rgba(2, 4, 10, 0.96)";
109+
ctx.fillRect(0, 0, w, h);
110+
if (count < 2) return { pointsCount: count, truncated: false };
111+
112+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
113+
for (let k = 0; k < count; k++) {
114+
const x = sommets[PAS_SOMMET * k];
115+
const y = sommets[PAS_SOMMET * k + 1];
116+
if (x < minX) minX = x;
117+
if (x > maxX) maxX = x;
118+
if (y < minY) minY = y;
119+
if (y > maxY) maxY = y;
120+
}
121+
const scale = Math.min(
122+
(w - 28) / Math.max(1, maxX - minX),
123+
(h - 28) / Math.max(1, maxY - minY),
124+
);
125+
ctx.strokeStyle = "#00d4ff";
126+
ctx.lineWidth = 1.35;
127+
ctx.beginPath();
128+
for (let k = 0; k < count; k++) {
129+
const px = 14 + (sommets[PAS_SOMMET * k] - minX) * scale;
130+
const py = h - 14 - (sommets[PAS_SOMMET * k + 1] - minY) * scale;
131+
if (k === 0 || sommets[PAS_SOMMET * k + 2] === 1) ctx.moveTo(px, py);
132+
else ctx.lineTo(px, py);
133+
}
134+
ctx.stroke();
135+
return { pointsCount: count, truncated: count >= CAPACITE_CHAINE };
136+
} catch (err) {
137+
console.warn("[atelier-lsysteme] WASM indisponible :", err);
138+
return { pointsCount: 0, truncated: false, erreur: true };
139+
}
140+
}

0 commit comments

Comments
 (0)