|
| 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