Software libero a scuola/Minetest/Luanti a scuola
Usare Luanti/Minetest per giocare a conoscere il territorio e, volendo, progettarne gli sviluppi futuri.
Il Progetto: La scuola come laboratorio di pianificazione territoriale
[modifica | modifica sorgente]Questo manuale nasce con l'obiettivo di guidare docenti e studenti nell'utilizzo di Luanti (precedentemente noto come Minetest) come strumento didattico, integrando la tecnologia, la geografia (GIS), la matematica e l'educazione civica.
Il cuore del progetto si articola su tre pilastri fondamentali:
- Esplorare e Osservare (La Georeferenziazione): Creare un mondo digitale che sia il facsimile esatto del proprio territorio reale. Attraverso l'importazione di dati geografici e altimetrici reali, gli studenti possono muoversi in scala 1:1 in un ambiente virtuale familiare, imparando a leggere la morfologia della propria terra da una prospettiva inedita e immersiva.
- Progettare ed Eseguire (La Ricostruzione): Permettere agli studenti di ricostruire l'ambiente, i monumenti e i complessi urbani. Questo processo stimola lo sviluppo di competenze geometriche, di calcolo spaziale e di problem-solving strutturale, costringendo il ragazzo a passare dall'osservazione della realtà alla sua astrazione e successiva esecuzione tridimensionale a blocchi.
- Partecipare (La Cittadinanza Attiva): Dare ai ragazzi la possibilità di utilizzare il server condiviso come un laboratorio democratico per la pianificazione urbana e infrastrutturale collettiva. Gli studenti non si limitano a replicare il presente o il passato, ma possono proporre modifiche, nuove infrastrutture e varianti urbanistiche, imparando il valore del dibattito, della negoziazione e della progettazione comunitaria del bene comune.
Attraverso questo percorso, il mondo voxel di Luanti smette di essere un semplice videogioco e diventa un vero e proprio gemello digitale didattico, un ponte tra la scuola e il territorio in grado di formare cittadini consapevoli e tecnicamente preparati.
Il Valore del Software Libero e l'Accessibilità
[modifica | modifica sorgente]L'adozione di software libero come Luanti elimina alla radice l'ostacolo delle licenze commerciali e dei costi per le scuole e le famiglie. Questa libertà tecnologica si traduce in una straordinaria agilità operativa: permette di configurare, personalizzare e pubblicare online un server scolastico in modo immediato. Per gli studenti, l'accesso a questo "gemello digitale" del territorio diventa sicuro e inclusivo: chiunque, da qualsiasi dispositivo a casa o a scuola, può connettersi liberamente e partecipare al progetto collettivo, con l'unico requisito di inserire il proprio nome utente e password personalizzati.
Flusso di lavoro: Dati GIS, Modellazione 3D e Iniezione in Luanti
[modifica | modifica sorgente]Questo capitolo documenta passo dopo passo la pipeline tecnologica utilizzata per trasformare i dati geografici e altimetrici reali della Val di Ledro in un mondo 3D interamente calpestabile all'interno del server Luanti (Minetest).
Fase 1: Acquisizione dei dati geografici e modellazione in Blender
[modifica | modifica sorgente]1. Selezione dell'Area di Interesse (OSM)
[modifica | modifica sorgente]Il progetto ha inizio con la delimitazione della zona geografica. Utilizzando il software di modellazione 3D Blender combinato con l'add-on Blender-GIS, è stato possibile connettersi direttamente ai server cartografici globali. Tramite l'interfaccia di georeferenziazione, è stato ritagliato il perimetro esatto della Val di Ledro attingendo alla cartografia vettoriale di OpenStreetMap (OSM). Questa operazione fissa le coordinate di base (Latitudine/Longitudine) nel sistema di riferimento del progetto.
2. Generazione del Rilievo Altimetrico (SRTM)
[modifica | modifica sorgente]Una volta ottenuta la mappa bidimensionale, si è passati alla terza dimensione applicando i dati altimetrici reali. Sfruttando la funzione di importazione DEM (Digital Elevation Model) di Blender-GIS, sono stati scaricati i dati SRTM (Shuttle Radar Topography Mission) forniti dalla NASA. Il plugin ha proiettato questi dati vettoriali sulla mesh piana di Blender, deformandola e generando un modello tridimensionale fedele del rilievo montuoso e della morfologia della valle.
3. Download dei Volumi degli Edifici
[modifica | modifica sorgente]Per integrare l'elemento antropico, è stato effettuato un download mirato direttamente dal portale ufficiale openstreetmap.org. È stato esportato un file con estensione .osm, contenente tutti i metadati e le impronte planimetriche degli edifici (i "volumi" e i poligoni delle case) registrati all'interno della medesima area geografica della valle.
4. Posizionamento e Georeferenziazione delle Case
[modifica | modifica sorgente]Il file .osm grezzo è stato importato all'interno della scena di Blender, sempre tramite l'ausilio di Blender-GIS. Il plugin, riconoscendo la corrispondenza delle coordinate geografiche condivise, ha posizionato automaticamente e matematicamente i volumi geometrici degli edifici sopra il rilievo 3D precedentemente generato, incollando le fondamenta delle case alle rispettive quote del terreno.
5. Esportazione della Mesh del Terreno
[modifica | modifica sorgente]Isolando la sola mesh geometrica del terreno (il rilievo montuoso), si è proceduto all'esportazione del modello in un formato di interscambio testuale standard. Il file risultante è stato salvato con il nome di LedroRilievo.obj, contenente l'elenco puro dei vertici tridimensionali del terreno.
6. Esportazione della Mesh degli Edifici
[modifica | modifica sorgente]Eseguendo la medesima procedura di isolamento, sono stati selezionati esclusivamente i poligoni e i volumi associati alle strutture architettoniche. L'esportazione ha generato il secondo file geometrico sorgente, denominato LedroCase.obj.
Fase 2: Parsing, Ottimizzazione e Struttura Server
[modifica | modifica sorgente]Il problema tecnologico
[modifica | modifica sorgente]I metodi tradizionali di scrittura dei nodi in Luanti via API standard (come `minetest.set_node`) mostrano forti limiti quando si gestiscono milioni di coordinate geometriche:
- Timeout UDP e Disconnessioni: L'elaborazione lineare di file massivi blocca il thread principale del server, causando il congelamento della rete e l'espulsione dei client.
- Errore Map::getNodeMetadata (Block not found): In ambienti a mappa vuota (es. mapgen `singlenode`), il server fallisce l'accesso ai metadati di blocchi non ancora inizializzati sul database del mondo (`map.sqlite`), generando migliaia di warning e fallendo la scrittura.
- Saturazione RAM in Blender: L'uso di modificatori come il Remesh su mesh territoriali molto estese (es. 20x12 km) satura la memoria volatile del sistema operativo, causando il crash del software di modellazione.
Architettura della Soluzione
[modifica | modifica sorgente]La pipeline definitiva si divide in due fasi staccate: un'ottimizzazione matematica dei dati in locale tramite Python e un'iniezione asincrona a basso livello nel server Luanti tramite Lua.
Fase 1: Estrazione e Interpolazione dei Dati (Python)
[modifica | modifica sorgente]Invece di sovraccaricare il motore di gioco o Blender, si utilizza uno script Python esterno sfruttando la libreria SciPy e l'algoritmo `NearestNDInterpolator` (o `LinearNDInterpolator`).
Lo script esegue le seguenti operazioni:
- Legge i vertici geometrici (`v X Y Z`) dal file `.obj` originale.
- Rileva i confini geografici assoluti della mappa.
- Inizializza un interpolatore spaziale e proietta i dati su una griglia fitta bidimensionale regolare (Heightmap) con un passo stabilito (es. ogni 2 metri).
- Tappa matematicamente i "buchi di cielo" presenti nella mesh rada, assegnando a ogni coordinata vuota l'altezza corretta calcolata sui punti vicini.
- Genera un file di testo ordinato (`ledro_terreno_dati.txt`) in formato `X,Y,Z`.
7. Conversione e Densificazione dei Dati (Python e SciPy)
[modifica | modifica sorgente]I file .obj nativi presentano una struttura a nuvola di punti (Point Cloud) rada e non ordinata. Con il supporto di Gemini, sono stati sviluppati gli script Python per elaborare i dati:
estrai_griglia.py: Questo script utilizza la libreria scientifica SciPy (in particolare l'algoritmoNearestNDInterpolator) per analizzare i 66.481 vertici originali distribuiti su un'area di circa 20x12 km. Python proietta questi punti su una griglia fitta bidimensionale regolare (Heightmap) con un passo costante (es. ogni 2 metri), interpolando matematicamente le altezze nei punti vuoti per "tappare i buchi di cielo" e generando un file coordinato e continuo chiamatoledro_terreno_dati.txtin formatoX,Y,Z.import os import numpy as np from scipy.interpolate import NearestNDInterpolator FILE_OBJ = "LedroRilievo.obj" FILE_OUTPUT = "ledro_terreno_dati.txt" PASSO = 2 # Un punto ogni 2 metri print("1. Lettura di tutti i vertici dal file OBJ...") punti_validi = [] with open(FILE_OBJ, "r") as f: for line in f: if line.startswith("v "): parti = line.split() if len(parti) >= 4: try: punti_validi.append((float(parti[1]), float(parti[2]), float(parti[3]))) except ValueError: continue if not punti_validi: print("ERRORE: Nessun vertice trovato nel file OBJ!") exit() # Coordinate separate x_arr = np.array([p[0] for p in punti_validi]) y_arr = np.array([p[1] for p in punti_validi]) z_arr = np.array([p[2] for p in punti_validi]) min_x, max_x = int(np.min(x_arr)), int(np.max(x_arr)) min_z, max_z = int(np.min(z_arr)), int(np.max(z_arr)) print(f"Confini rilevati -> X: [{min_x} a {max_x}], Z: [{min_z} a {max_z}]") print("2. Inizializzazione interpolatore geometrico (SciPy)...") # Creiamo l'interpolatore basato sui punti noti dell'OBJ interp = NearestNDInterpolator(list(zip(x_arr, z_arr)), y_arr) print("3. Generazione griglia fitta continua e scrittura...") # Generiamo gli intervalli regolari basati sul PASSO scelto intervallo_x = np.arange(round(min_x / PASSO) * PASSO, round(max_x / PASSO) * PASSO + PASSO, PASSO) intervallo_z = np.arange(round(min_z / PASSO) * PASSO, round(max_z / PASSO) * PASSO + PASSO, PASSO) punti_totali_scritti = 0 with open(FILE_OUTPUT, "w") as out: for gx in intervallo_x: # Calcoliamo l'altezza per l'intera colonna Z in un colpo solo (velocissimo) gz_array = np.array(list(intervallo_z)) gx_array = np.full_like(gz_array, gx) # SciPy calcola le altezze interpolate istantaneamente per tutta la riga altezze_interpolate = interp(gx_array, gz_array) for i, gz in enumerate(intervallo_z): gy = round(altezze_interpolate[i]) out.write(f"{gx},{gy},{gz}\n") punti_totali_scritti += 1 print(f"Operazione completata! Generata griglia solida continua con {punti_totali_scritti} punti complessivi.")
ledro_convertitore_case.py: Estrae le posizioni degli edifici associandole ai rispettivi blocchi di costruzione, generando il file ordinatoledro_case_dati.txtimport numpy as np import trimesh import os from math import ceil # ========================================== # CONFIGURAZIONE COSTANTI # ========================================== FILE_OBJ_TERRENO = "LedroRilievo.obj" FILE_OBJ_CASE = "LedroCase.obj" NOME_FILE_USCITA = "ledro_case_dati.txt" # Scala 1.0 per corrispondenza esatta con il terreno a passo 1 metro FAUTORE_SCALA = 1.0 START_Y = 30 def converti_case_streaming(terreno_input, case_input, file_output): if not os.path.exists(case_input) or not os.path.exists(terreno_input): print("Errore: Uno dei file OBJ non esiste nella cartella attuale!") return # 1. Calcolo del centro del terreno per l'allineamento perfetto degli assi print(f"Lettura terreno per allineamento assi: {terreno_input}...") scena_t = trimesh.load(terreno_input, force='mesh') mesh_t = trimesh.util.concatenate([g for g in scena_t.geometry.values()]) if isinstance(scena_t, trimesh.Scene) else scena_t bounds_t = mesh_t.bounds min_x_t, _, min_z_t = bounds_t[0] max_x_t, _, max_z_t = bounds_t[1] centro_x = int((max_x_t - min_x_t) / 2) centro_z = int((max_z_t - min_z_t) / 2) # 2. Caricamento e pulizia della mesh degli edifici print(f"Caricamento edifici da {case_input}...") scena_c = trimesh.load(case_input, force='mesh') if isinstance(scena_c, trimesh.Scene): mesh_unica_case = trimesh.util.concatenate([g for g in scena_c.geometry.values()]) else: mesh_unica_case = scena_c print("Pulizia della mesh e fusione dei vertici adiacenti...") # Valida e salda insieme i poligoni staccati della stessa struttura mesh_unica_case.process(validate=True) print("Separazione degli edifici in componenti connesse...") edifici_grezzi = mesh_unica_case.split(only_watertight=False) # Filtriamo le bricioline geometriche inferiori a 2mq edifici = [edf for edf in edifici_grezzi if edf.area > 2.0] print(f"Trovati {len(edifici)} edifici reali stabili. Generazione geometrie cave...") # Rimuoviamo il vecchio file se presente if os.path.exists(file_output): os.remove(file_output) # 3. Ciclo di generazione geometrica delle case cave (SOLO SOLIDI) edifici_scritti = 0 with open(file_output, "w") as f: for i, edf in enumerate(edifici): b = edf.bounds min_x, min_y, min_z = b[0] max_x, max_y, max_z = b[1] dim_x = int(ceil(max_x - min_x)) dim_y = int(ceil(max_y - min_y)) dim_z = int(ceil(max_z - min_z)) # FILTRO DI SICUREZZA ANTI-LOOP (ARTEFATTI GIS): # Se un "edificio" supera le dimensioni di un enorme isolato, è un errore di mesh dell'OBJ. # Lo saltiamo per evitare che i cicli for successivi congelino la CPU. if dim_x > 60 or dim_z > 60 or dim_y > 40: continue # APPLICAZIONE REGOLE PUNTO 4: Vincoli minimi (5x6x5) larghezza_x = max(5, dim_x) lunghezza_z = max(6, dim_z) altezza_y = max(5, dim_y) # Calcolo coordinate assolute centrate rispetto al terreno start_x = int(min_x - min_x_t - centro_x) start_z = int(min_z - min_z_t - centro_z) start_y = int(min_y + START_Y) edifici_scritti += 1 # Rendering dei soli nodi fisici perimetrali for dx in range(larghezza_x): for dz in range(lunghezza_z): for dy in range(altezza_y): abs_x = start_x + dx abs_z = start_z + dz abs_y = start_y + dy is_muro = (dx == 0 or dx == larghezza_x - 1 or dz == 0 or dz == lunghezza_z - 1) is_pavimento = (dy == 0) is_tetto = (dy == altezza_y - 1) if is_pavimento: blocco = "default:wood" elif is_tetto: blocco = "default:brick" elif is_muro: blocco = "default:stone" else: # Salta l'aria interna! Risparmia lo spazio su disco continue f.write(f"{abs_x},{abs_y},{abs_z},{blocco}\n") if (i + 1) % 100 == 0 or (i + 1) == len(edifici): print(f"Progressione: {i + 1}/{len(edifici)} nodi mesh analizzati... ({edifici_scritti} edifici reali scritti)") print(f"Fatto! File delle case salvato con successo in {file_output}. Scritti {edifici_scritti} edifici.") # ========================================== # PUNTO DI INGRESSO DELLO SCRIPT # ========================================== if __name__ == "__main__": converti_case_streaming(FILE_OBJ_TERRENO, FILE_OBJ_CASE, NOME_FILE_USCITA)
8. Configurazione dell'Ambiente Virtuale Docker
[modifica | modifica sorgente]
Per ospitare l'infrastruttura di simulazione in modo isolato e replicabile, è stato configurato un container Docker gestito tramite Docker Compose. Sulla base di un file docker-compose.yml strutturato ad hoc, la cartella di lavoro del server Luanti/Minetest è stata mappata in locale sul PC dell'utente (tramite i Docker Volumes) per permettere la modifica in tempo reale dei file di gioco. All'interno della configurazione, il mondo è stato battezzato ledro3d e l'utente mattruffoni è stato designato come amministratore di sistema con massimi privilegi energetici.
services:
luanti_engine:
image: lscr.io/linuxserver/luanti:latest
container_name: local_luanti_engine
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Rome
# FORZATURA: Diciamo a Luanti esattamente quale file .conf leggere dentro il container
- CLI_ARGS=--worldname ledro3d --config /config/.minetest/minetest.conf --terminal
volumes:
- ./server_data:/config/.minetest
ports:
- 30000:30000/udp
restart: unless-stopped
9. Accessibilità del Server di Gioco
[modifica | modifica sorgente]Una volta avviato lo stack di Docker tramite terminale (docker compose up -d), il container ha esposto i servizi di rete dedicati. Il server Luanti/Minetest è diventato operativo e raggiungibile in locale dall'indirizzo di loopback localhost:30000, pronto ad accettare le connessioni da parte del client di gioco per l'esplorazione virtuale.
Fase 3: Sviluppo della Mod e Generazione del Mondo
[modifica | modifica sorgente]Creazione mod importatore
[modifica | modifica sorgente]Nella cartella .../minetest-server/server_data/worlds/ledro3d/worldmods/importatore si crea la mod importatore inserendoci il file init.lua, mod.conf e i due file di testo ledro_terreno_dati.txt e ledro_case_dati.txt creati in precedenza.
10. Sviluppo della Mod Lua Asincrona "Importatore"
[modifica | modifica sorgente]L'iniezione di milioni di nodi all'interno del database del mondo (map.sqlite) presenta forti criticità prestazionali (rischio di timeout UDP e crash per memoria satura). Per ovviare a questo problema, è stata sviluppata una mod personalizzata in linguaggio Lua denominata importatore. La mod implementa due soluzioni architetturali cruciali:
- Il VoxelManip Engine (LVM): Invece di posizionare un singolo cubo alla volta, la mod alloca grandi porzioni di mappa direttamente nella memoria volatile del server, scrive i blocchi di erba superficiale (
default:dirt_with_grass) e le fondamenta sottostanti (default:stone), e forza la scrittura in blocco sul database, azzerando i warning di blocchi non trovati. - Gestione tramite Co-routine Lua: La lettura dei file
.txtè inserita in un ciclo asincrono controllato da co-routine. Tramite l'istruzionecoroutine.yield(), la mod rilascia il controllo alla CPU del PC host ogni 100 nodi processati, permettendo al server di rimanere fluido, gestire i pacchetti di rete e consentire il movimento del giocatore durante la crescita progressiva della valle.
Fase 2: Iniezione Asincrona nella Mappa (Luanti Mod)
[modifica | modifica sorgente]La mod Luanti (scritta in `init.lua`) riceve il file a griglia regolare e applica due strategie fondamentali per garantire stabilità e performance:
- Uso del VoxelManip (LVM): Invece di scrivere un blocco alla volta, la mod alloca un'area di memoria temporanea (`minetest.get_voxel_manip()`), manipola l'array dei nodi inserendo la superficie erbosa (`default:dirt_with_grass`) e lo zoccolo di roccia sottostante (`default:stone`), e infine forza la scrittura dell'intero blocco direttamente sul database (`vm:write_to_map()`). Questo bypassa i controlli dei metadati azzerando il warning Block not found.
- Gestione Asincrona via Co-routine: Il ciclo di lettura del file viene inserito in una co-routine Lua (`coroutine.create`). Tramite l'istruzione `coroutine.yield()`, la mod rilascia il controllo alla CPU ogni 100 nodi processati, permettendo al server Luanti di gestire i pacchetti di rete, l'orario del mondo e il login dei giocatori senza lag.
Codice init.lua della mod importatore
[modifica | modifica sorgente]-- ========================================================
-- 1. PRIVILEGI AUTOMATICI PER MATTRUFFONI (CON SETTIME)
-- ========================================================
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
if name == "mattruffoni" then
minetest.after(0.5, function()
local p = minetest.get_player_by_name(name)
if p then
local privs = minetest.get_player_privs(name)
privs.privs = true
privs.fly = true
privs.fast = true
privs.teleport = true
privs.interact = true
privs.shout = true
privs.settime = true -- Aggiunto privilegio per impostare l'ora
privs.time = true -- Aggiunto privilegio per gestire il tempo
minetest.set_player_privs(name, privs)
minetest.chat_send_player(name, "Benvenuto Prof! Privilegi completi (incluso settime) attivati.")
end
end)
end
end)
-- ========================================================
-- 2. PIPELINE DI IMPORTAZIONE A GRIGLIA ORDINATA (LVM)
-- ========================================================
local function avvia_importazione_superficie()
local percorso_terreno = minetest.get_modpath("importatore") .. "/ledro_terreno_dati.txt"
local percorso_case = minetest.get_modpath("importatore") .. "/ledro_case_dati.txt"
local thread = coroutine.create(function()
-- A. TERRENO (Morfologia a celle raccordate)
local file_t = io.open(percorso_terreno, "r")
if file_t then
minetest.log("action", "[Importatore] Scrittura griglia terreno georeferenziato...")
local count = 0
for line in file_t:lines() do
local x, y, z = line:match("([^,]+),([^,]+),([^,]+)")
if x and y and z then
local tx, ty, tz = tonumber(x), tonumber(y), tonumber(z)
-- Raggio 2 crea una piastra 5x5 attorno a ogni punto.
-- Avendo ordinato il file a passo 2, le piastre si fondono perfettamente.
local raggio = 2
-- Allochiamo l'area minima per il manipolatore di voxel
local p_min = {x = tx - raggio, y = ty - 15, z = tz - raggio}
local p_max = {x = tx + raggio, y = ty, z = tz + raggio}
local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(p_min, p_max)
local data = vm:get_data()
local va = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
local c_grass = minetest.get_content_id("default:dirt_with_grass")
local c_stone = minetest.get_content_id("default:stone")
-- Disegniamo la piastra locale
for dx = -raggio, raggio do
for dz = -raggio, raggio do
local px = tx + dx
local pz = tz + dz
-- Blocco d'erba superficiale
local vi_g = va:index(px, ty, pz)
data[vi_g] = c_grass
-- Zoccolo di pietra sottostante (15 blocchi per saldare i dislivelli)
for dy = 1, 15 do
local vi_s = va:index(px, ty - dy, pz)
data[vi_s] = c_stone
end
end
end
-- Scrittura forzata nel database senza controlli intermedi
vm:set_data(data)
vm:write_to_map()
count = count + 1
-- Rilasciamo la CPU ogni 100 punti della griglia per mantenere stabile il server
if count % 100 == 0 then
minetest.log("action", "[Importatore] Punti griglia terreno consolidati: " .. count)
coroutine.yield()
end
end
end
file_t:close()
end
-- B. CASE (Puntuali sopra il terreno)
local file_c = io.open(percorso_case, "r")
if file_c then
minetest.log("action", "[Importatore] Iniezione volumi edifici...")
local count = 0
for line in file_c:lines() do
local x, y, z, blockname = line:match("([^,]+),([^,]+),([^,]+),([^,]+)")
if x and y and z and blockname then
local pos = {x = tonumber(x), y = tonumber(y), z = tonumber(z)}
blockname = blockname:gsub("%s+", "")
local vm = minetest.get_voxel_manip()
vm:read_from_map(pos, pos)
minetest.set_node(pos, {name = blockname})
count = count + 1
if count % 2000 == 0 then
minetest.log("action", "[Importatore] Blocchi edificio inseriti: " .. count)
coroutine.yield()
end
end
end
file_c:close()
end
minetest.log("action", "[Importatore] PIPELINE COMPLETATA CON SUCCESSO!")
minetest.chat_send_all("[Importatore] PIPELINE COMPLETATA! La Val di Ledro a griglia è pronta.")
end)
local function esegui_passo()
if coroutine.status(thread) ~= "dead" then
coroutine.resume(thread)
minetest.after(0.02, esegui_passo)
end
end
esegui_passo()
end
-- ========================================================
-- 3. ATTIVAZIONE EVENTO AL LOGIN DI MATTRUFFONI
-- ========================================================
local attivato = false
minetest.register_on_joinplayer(function(player)
if player:get_player_name() == "mattruffoni" and not attivato then
attivato = true
minetest.after(3.0, function()
minetest.chat_send_all("[Importatore] Avvio rigenerazione morfologica a griglia fitta...")
avvia_importazione_superficie()
end)
end
end)
Codice mod.conf
[modifica | modifica sorgente]name = importatore
description = Importatore geometrico per la Val di Ledro
Risultati e Sviluppi Pedagogici
[modifica | modifica sorgente]L'applicazione di questo flusso permette di generare manti continui di milioni di blocchi mantenendo il server Docker estremamente leggero. Questo ambiente offre uno scenario di apprendimento eccezionale per la didattica laboratoriale (Matematica e Scienze), permettendo agli studenti di esplorare modelli in scala 1:1 del proprio territorio, comprendere l'interazione tra coordinate cartesiane e dati GIS, e interagire con elementi di programmazione e gestione server.
Note
[modifica | modifica sorgente]Ovviamente tutto è stato sviluppato con AI, nello specifico Gemini.