// Simple Node.js (ESM) script to bake a grayscale heightmap PNG
// from the OBJ terrain model: resources/models/Terrain003_1K.obj
//
// Usage (from repo root, after installing pngjs):
//   npm install pngjs
//   node tools/generate_terrain_heightmap.js
//
// It will write a PNG into: resources/textures/heightmap/Terrain003_1K_Height512.png

import fs			from "node:fs";
import path		from "node:path";

import { PNG }	from "pngjs";


const ROOT_DIR		= path.dirname( path.dirname( new URL( import.meta.url ).pathname ) );

const OBJ_PATH		= path.join( ROOT_DIR, "resources", "models", "Terrain003_1K.obj" );

const OUT_PATH		= path.join( ROOT_DIR, "resources", "textures", "heightmap", "Terrain003_1K_Height512.png" );

const RESOLUTION	= 512;


function parseObjVertices( text ) {

	const lines		= text.split( /\r?\n/ );

	const vertices	= [];

	for ( let i = 0; i < lines.length; i++ ) {

		const line = lines[ i ].trim();

		if ( line.length === 0 || line.charAt( 0 ) === "#" ) {

			continue;

		}

		const parts = line.split( /\s+/ );

		if ( parts[ 0 ] === "v" && parts.length >= 4 ) {

			const x = parseFloat( parts[ 1 ] );
			const y = parseFloat( parts[ 2 ] );
			const z = parseFloat( parts[ 3 ] );

			if ( !isNaN( x ) && !isNaN( y ) && !isNaN( z ) ) {

				vertices.push( { x, y, z } );

			}

		}

	}

	return vertices;

}


function buildHeightmapFromVertices( vertices, resolution ) {

	if ( vertices.length === 0 ) {

		throw new Error( "No vertices parsed from OBJ." );

	}

	let minX = Infinity, maxX = -Infinity;
	let minY = Infinity, maxY = -Infinity;
	let minZ = Infinity, maxZ = -Infinity;

	for ( const v of vertices ) {

		if ( v.x < minX ) minX = v.x;
		if ( v.x > maxX ) maxX = v.x;
		if ( v.y < minY ) minY = v.y;
		if ( v.y > maxY ) maxY = v.y;
		if ( v.z < minZ ) minZ = v.z;
		if ( v.z > maxZ ) maxZ = v.z;

	}

	const spanX	= maxX - minX || 1;
	const spanZ	= maxZ - minZ || 1;

	const width	= resolution;
	const height= resolution;

	const heights	= new Float32Array( width * height );
	const counts	= new Uint32Array( width * height );

	for ( const v of vertices ) {

		const uNorm	= ( v.x - minX ) / spanX;
		const vNorm	= ( v.z - minZ ) / spanZ;

		let ix		= Math.round( uNorm * ( width - 1 ) );
		let iy		= Math.round( vNorm * ( height - 1 ) );

		if ( ix < 0 ) ix = 0;
		if ( ix >= width ) ix = width - 1;
		if ( iy < 0 ) iy = 0;
		if ( iy >= height ) iy = height - 1;

		const idx	= iy * width + ix;

		heights[ idx ] += v.y;
		counts[ idx ]++;

	}

	for ( let i = 0; i < heights.length; i++ ) {

		if ( counts[ i ] > 0 ) {

			heights[ i ] /= counts[ i ];

		} else {

			heights[ i ] = minY;

		}

	}

	const tmp = new Float32Array( width * height );

	for ( let pass = 0; pass < 2; pass++ ) {

		for ( let y = 0; y < height; y++ ) {

			for ( let x = 0; x < width; x++ ) {

				let sum		= 0;
				let weight	= 0;

				for ( let dy = -1; dy <= 1; dy++ ) {

					for ( let dx = -1; dx <= 1; dx++ ) {

						const nx = x + dx;
						const ny = y + dy;

						if ( nx < 0 || ny < 0 || nx >= width || ny >= height ) {
							continue;
						}

						const nIdx = ny * width + nx;

						sum		+= heights[ nIdx ];
						weight	+= 1;

					}

				}

				const idx = y * width + x;

				tmp[ idx ] = weight > 0 ? sum / weight : heights[ idx ];

			}

		}

		for ( let i = 0; i < heights.length; i++ ) {

			heights[ i ] = tmp[ i ];

		}

	}

	let outMin = Infinity;
	let outMax = -Infinity;

	for ( let i = 0; i < heights.length; i++ ) {

		const h = heights[ i ];

		if ( h < outMin ) outMin = h;
		if ( h > outMax ) outMax = h;

	}

	const range = outMax - outMin || 1;

	const normalized = new Float32Array( width * height );

	for ( let i = 0; i < heights.length; i++ ) {

		normalized[ i ] = ( heights[ i ] - outMin ) / range;

	}

	return { width, height, data: normalized };

}


function writeHeightmapPng( heightmap, outPath ) {

	const { width, height, data } = heightmap;

	const png = new PNG( { width, height } );

	for ( let y = 0; y < height; y++ ) {

		for ( let x = 0; x < width; x++ ) {

			const idx	= y * width + x;

			const v		= Math.max( 0, Math.min( 1, data[ idx ] ) );

			const c		= Math.round( v * 255 );

			const o		= idx * 4;

			png.data[ o + 0 ] = c;
			png.data[ o + 1 ] = c;
			png.data[ o + 2 ] = c;
			png.data[ o + 3 ] = 255;

		}

	}

	return new Promise( function( resolve, reject ) {

		const stream = fs.createWriteStream( outPath );

		stream.on( "finish", resolve );
		stream.on( "error", reject );

		png.pack().pipe( stream );

	} );

}


async function main( ) {

	try {

		console.log( "Reading OBJ:", OBJ_PATH );

		const text		= fs.readFileSync( OBJ_PATH, "utf8" );

		const vertices	= parseObjVertices( text );

		console.log( "Parsed vertices:", vertices.length );

		const heightmap	= buildHeightmapFromVertices( vertices, RESOLUTION );

		console.log( "Heightmap built:", heightmap.width, "x", heightmap.height );

		await writeHeightmapPng( heightmap, OUT_PATH );

		console.log( "Heightmap written to:", OUT_PATH );

	} catch ( err ) {

		console.error( "Failed to generate heightmap:", err );

		process.exitCode = 1;

	}

}


if ( import.meta.url === `file://${ process.argv[ 1 ] }` ) {

	main( );

}
