// MinecraftPT texture generator // Generates noise.png (blue noise), CloudNoise 3D bins, and ripple.png const fs = require('fs'); const path = require('path'); const zlib = require('zlib'); const outDir = path.join(__dirname, 'texture'); fs.mkdirSync(outDir, { recursive: true }); // ---------- PNG encoder (minimal, non-interlaced RGBA8) ---------- function crc32(buf) { let table = crc32.table; if (!table) { table = crc32.table = new Int32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; table[n] = c; } } let crc = -1; for (let i = 0; i < buf.length; i++) crc = (crc >>> 8) ^ table[(crc ^ buf[i]) & 0xff]; return (crc ^ -1) >>> 0; } function chunk(type, data) { const len = Buffer.alloc(4); len.writeUInt32BE(data.length); const typeBuf = Buffer.from(type, 'ascii'); const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(Buffer.concat([typeBuf, data]))); return Buffer.concat([len, typeBuf, data, crc]); } function encodePNG(width, height, rgba) { const sig = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4); ihdr[8] = 8; // bit depth ihdr[9] = 6; // color type RGBA const raw = Buffer.alloc((width * 4 + 1) * height); for (let y = 0; y < height; y++) { raw[y * (width * 4 + 1)] = 0; // filter none rgba.copy(raw, y * (width * 4 + 1) + 1, y * width * 4, (y + 1) * width * 4); } const idat = zlib.deflateSync(raw, { level: 9 }); return Buffer.concat([sig, chunk('IHDR', ihdr), chunk('IDAT', idat), chunk('IEND', Buffer.alloc(0))]); } // ---------- Hashing ---------- function hash1(x, y, z, seed) { let h = (x * 374761393 + y * 668265263 + z * 2246822519 + seed * 3266489917) | 0; h = (h ^ (h >>> 13)) | 0; h = Math.imul(h, 1274126177); h = (h ^ (h >>> 16)) >>> 0; return h / 4294967296; } // ---------- 1. Blue noise 128x128 ---------- { const S = 128; const buf = Buffer.alloc(S * S * 4); const noise = new Float32Array(S * S); // white noise for (let y = 0; y < S; y++) for (let x = 0; x < S; x++) noise[y * S + x] = hash1(x, y, 7, 12345); // high-pass: n - blur(n), renormalize -> bluish const blurred = new Float32Array(S * S); for (let y = 0; y < S; y++) { for (let x = 0; x < S; x++) { let sum = 0; for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) { const xx = (x + dx + S) % S; const yy = (y + dy + S) % S; sum += noise[yy * S + xx]; } blurred[y * S + x] = sum / 9; } } let min = 1e9, max = -1e9; for (let i = 0; i < S * S; i++) { const v = noise[i] - blurred[i]; noise[i] = v; if (v < min) min = v; if (v > max) max = v; } for (let y = 0; y < S; y++) { for (let x = 0; x < S; x++) { const v = (noise[y * S + x] - min) / (max - min); const p = y * S * 4 + x * 4; buf[p] = Math.round(v * 255); buf[p + 1] = Math.round(v * 255); buf[p + 2] = Math.round(v * 255); buf[p + 3] = 255; } } fs.writeFileSync(path.join(outDir, 'noise.png'), encodePNG(S, S, buf)); console.log('noise.png generated'); } // ---------- 2. 3D value noise (128^3 RGBA8) ---------- function make3DNoise(W, H, D, seed) { const data = Buffer.alloc(W * H * D * 4); // Generate 3 octaves of value noise at lattice 8, 4, 2 const octaves = [ { lat: 8, amp: 0.6 }, { lat: 4, amp: 0.3 }, { lat: 2, amp: 0.1 }, ]; for (let z = 0; z < D; z++) { for (let y = 0; y < H; y++) { for (let x = 0; x < W; x++) { let v = 0; for (const o of octaves) { const L = o.lat; const fx = (x / W) * L; const fy = (y / H) * L; const fz = (z / D) * L; const x0 = Math.floor(fx), y0 = Math.floor(fy), z0 = Math.floor(fz); const x1 = x0 + 1, y1 = y0 + 1, z1 = z0 + 1; const tx = fx - x0, ty = fy - y0, tz = fz - z0; const sx = tx * tx * (3 - 2 * tx); const sy = ty * ty * (3 - 2 * ty); const sz = tz * tz * (3 - 2 * tz); const c000 = hash1(x0, y0, z0, seed + o.lat); const c100 = hash1(x1, y0, z0, seed + o.lat); const c010 = hash1(x0, y1, z0, seed + o.lat); const c110 = hash1(x1, y1, z0, seed + o.lat); const c001 = hash1(x0, y0, z1, seed + o.lat); const c101 = hash1(x1, y0, z1, seed + o.lat); const c011 = hash1(x0, y1, z1, seed + o.lat); const c111 = hash1(x1, y1, z1, seed + o.lat); const x00 = c000 + (c100 - c000) * sx; const x10 = c010 + (c110 - c010) * sx; const x01 = c001 + (c101 - c001) * sx; const x11 = c011 + (c111 - c011) * sx; const y0v = x00 + (x10 - x00) * sy; const y1v = x01 + (x11 - x01) * sy; v += (y0v + (y1v - y0v) * sz) * o.amp; } const idx = (z * H + y) * W * 4 + x * 4; data[idx] = Math.round(v * 255); data[idx + 1] = Math.round(v * 255); data[idx + 2] = Math.round(v * 255); data[idx + 3] = 255; } } } return data; } { const W = 128, H = 128, D = 128; const data = make3DNoise(W, H, D, 4242); fs.writeFileSync(path.join(outDir, 'CloudNoise_128_128_128.bin'), data); console.log('CloudNoise_128_128_128.bin generated'); } { const W = 32, H = 32, D = 32; const data = make3DNoise(W, H, D, 7777); // RGB8 only const rgb = Buffer.alloc(W * H * D * 3); for (let i = 0; i < W * H * D; i++) { rgb[i * 3] = data[i * 4]; rgb[i * 3 + 1] = data[i * 4 + 1]; rgb[i * 3 + 2] = data[i * 4 + 2]; } fs.writeFileSync(path.join(outDir, 'CloudNoise2_32_32_32.bin'), rgb); console.log('CloudNoise2_32_32_32.bin generated'); } // ---------- 3. Ripple 128x128 ---------- { const S = 128; const buf = Buffer.alloc(S * S * 4); for (let y = 0; y < S; y++) { for (let x = 0; x < S; x++) { const dx = (x - S / 2) / S; const dy = (y - S / 2) / S; const dist = Math.sqrt(dx * dx + dy * dy); // concentric ripple const n = hash1(x, y, 11, 999); const ripple = 0.5 + 0.5 * Math.sin(dist * 40 + n * 6.28); const p = (y * S + x) * 4; buf[p] = Math.round(ripple * 255); buf[p + 1] = Math.round(ripple * 255); buf[p + 2] = 128; buf[p + 3] = 255; } } fs.writeFileSync(path.join(outDir, 'ripple.png'), encodePNG(S, S, buf)); console.log('ripple.png generated'); } console.log('All textures generated in', outDir);