@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
292 lines (291 loc) • 11.2 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SeededRandom = void 0;
const ModelDesignUtilities_1 = __importDefault(require("./ModelDesignUtilities"));
/**
* Simple seeded pseudo-random number generator (Mulberry32).
* Produces deterministic sequences for consistent noise generation.
*/
class SeededRandom {
state;
constructor(seed) {
this.state = seed;
}
/**
* Generate next random number in [0, 1)
*/
next() {
let t = (this.state += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
/**
* Generate random integer in [min, max] inclusive
*/
nextInt(min, max) {
return Math.floor(this.next() * (max - min + 1)) + min;
}
}
exports.SeededRandom = SeededRandom;
/**
* 4x4 Bayer dithering matrix (normalized to 0-1 range)
*/
const BAYER_MATRIX_4X4 = [
[0 / 16, 8 / 16, 2 / 16, 10 / 16],
[12 / 16, 4 / 16, 14 / 16, 6 / 16],
[3 / 16, 11 / 16, 1 / 16, 9 / 16],
[15 / 16, 7 / 16, 13 / 16, 5 / 16],
];
/**
* Noise generation utilities - pure algorithms for procedural noise patterns.
*/
class NoiseGenerationUtilities {
/**
* Hash function to generate seed from string (djb2 algorithm)
*/
static hashString(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = (hash * 33) ^ str.charCodeAt(i);
}
return hash >>> 0; // Convert to unsigned 32-bit
}
/**
* Generate noise grid based on pattern type
*/
static generateNoiseGrid(pattern, colors, factor, width, height, rng, scale) {
switch (pattern) {
case "dither":
return this.generateDitherGrid(colors, factor, width, height);
case "perlin":
return this.generatePerlinGrid(colors, factor, width, height, rng, scale);
case "stipple":
return this.generateStippleGrid(colors, factor, width, height, rng);
case "gradient":
return this.generateGradientGrid(colors, width, height, rng);
case "random":
default:
return this.generateRandomGrid(colors, factor, width, height, rng);
}
}
/**
* Simple random noise - each pixel randomly picks from colors
*/
static generateRandomGrid(colors, factor, width, height, rng) {
const grid = [];
const baseColor = colors[0];
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) {
if (rng.next() < factor && colors.length > 1) {
// Pick a random color from the palette
const colorIndex = rng.nextInt(0, colors.length - 1);
row.push(colors[colorIndex]);
}
else {
row.push(baseColor);
}
}
grid.push(row);
}
return grid;
}
/**
* Ordered dithering using Bayer matrix
*/
static generateDitherGrid(colors, factor, width, height) {
const grid = [];
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) {
// Get threshold from Bayer matrix
const threshold = BAYER_MATRIX_4X4[y % 4][x % 4];
// Determine which color to use based on threshold and factor
const colorIndex = threshold * factor * (colors.length - 1);
const index = Math.min(Math.floor(colorIndex), colors.length - 1);
row.push(colors[index]);
}
grid.push(row);
}
return grid;
}
/**
* Perlin-like noise for organic variation
* Uses simplified value noise with interpolation and smooth color blending
*/
static generatePerlinGrid(colors, factor, width, height, rng, scale) {
// Generate base noise at lower resolution
const noiseWidth = Math.ceil(width / scale) + 2;
const noiseHeight = Math.ceil(height / scale) + 2;
const baseNoise = [];
for (let y = 0; y < noiseHeight; y++) {
const row = [];
for (let x = 0; x < noiseWidth; x++) {
row.push(rng.next());
}
baseNoise.push(row);
}
// Interpolate to full resolution
const grid = [];
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) {
// Get interpolated noise value
const nx = x / scale;
const ny = y / scale;
const value = this.bilinearInterpolate(baseNoise, nx, ny);
// Apply factor to control spread (factor 1.0 = full range, lower = more subtle)
const adjustedValue = (value - 0.5) * factor + 0.5;
const clampedValue = Math.max(0, Math.min(1, adjustedValue));
// Blend between colors smoothly instead of hard quantization
// Map value to position in color array and interpolate
const colorPos = clampedValue * (colors.length - 1);
const colorIndex = Math.floor(colorPos);
const colorFrac = colorPos - colorIndex;
const color1 = colors[Math.min(colorIndex, colors.length - 1)];
const color2 = colors[Math.min(colorIndex + 1, colors.length - 1)];
// Linearly interpolate between adjacent colors
const blendedColor = {
r: Math.round(color1.r * (1 - colorFrac) + color2.r * colorFrac),
g: Math.round(color1.g * (1 - colorFrac) + color2.g * colorFrac),
b: Math.round(color1.b * (1 - colorFrac) + color2.b * colorFrac),
a: Math.round(color1.a * (1 - colorFrac) + color2.a * colorFrac),
};
row.push(blendedColor);
}
grid.push(row);
}
return grid;
}
/**
* Bilinear interpolation for smooth noise
*/
static bilinearInterpolate(grid, x, y) {
const x0 = Math.floor(x);
const y0 = Math.floor(y);
const x1 = x0 + 1;
const y1 = y0 + 1;
const xFrac = x - x0;
const yFrac = y - y0;
// Get values at corners (with bounds checking)
const v00 = grid[Math.min(y0, grid.length - 1)]?.[Math.min(x0, grid[0].length - 1)] ?? 0;
const v10 = grid[Math.min(y0, grid.length - 1)]?.[Math.min(x1, grid[0].length - 1)] ?? 0;
const v01 = grid[Math.min(y1, grid.length - 1)]?.[Math.min(x0, grid[0].length - 1)] ?? 0;
const v11 = grid[Math.min(y1, grid.length - 1)]?.[Math.min(x1, grid[0].length - 1)] ?? 0;
// Interpolate
const top = v00 * (1 - xFrac) + v10 * xFrac;
const bottom = v01 * (1 - xFrac) + v11 * xFrac;
return top * (1 - yFrac) + bottom * yFrac;
}
/**
* Stipple pattern - scattered dots on base color
*/
static generateStippleGrid(colors, factor, width, height, rng) {
const grid = [];
const baseColor = colors[0];
const dotColors = colors.slice(1);
if (dotColors.length === 0) {
dotColors.push(baseColor);
}
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) {
// Stipple probability based on factor (factor of 0.5 = ~50% dots)
if (rng.next() < factor * 0.7) {
const dotIndex = rng.nextInt(0, dotColors.length - 1);
row.push(dotColors[dotIndex]);
}
else {
row.push(baseColor);
}
}
grid.push(row);
}
return grid;
}
/**
* Gradient noise - smooth transition between colors
* Creates horizontal or vertical gradient with slight noise
*/
static generateGradientGrid(colors, width, height, rng) {
const grid = [];
const isVertical = rng.next() > 0.5;
for (let y = 0; y < height; y++) {
const row = [];
for (let x = 0; x < width; x++) {
// Calculate position along gradient (0 to 1)
const t = isVertical ? y / Math.max(height - 1, 1) : x / Math.max(width - 1, 1);
// Add slight noise
const noiseT = t + (rng.next() - 0.5) * 0.1;
const clampedT = Math.max(0, Math.min(1, noiseT));
// Interpolate between colors
const colorPos = clampedT * (colors.length - 1);
const colorIndex = Math.floor(colorPos);
const colorFrac = colorPos - colorIndex;
const c1 = colors[Math.min(colorIndex, colors.length - 1)];
const c2 = colors[Math.min(colorIndex + 1, colors.length - 1)];
row.push(this.lerpColor(c1, c2, colorFrac));
}
grid.push(row);
}
return grid;
}
/**
* Linear interpolation between two colors
*/
static lerpColor(c1, c2, t) {
return {
r: Math.round(c1.r * (1 - t) + c2.r * t),
g: Math.round(c1.g * (1 - t) + c2.g * t),
b: Math.round(c1.b * (1 - t) + c2.b * t),
a: Math.round(c1.a * (1 - t) + c2.a * t),
};
}
/**
* Check if two colors are equal
*/
static colorsEqual(c1, c2) {
return c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a;
}
/**
* Convert parsed color to hex string
*/
static colorToHex(color) {
const r = color.r.toString(16).padStart(2, "0");
const g = color.g.toString(16).padStart(2, "0");
const b = color.b.toString(16).padStart(2, "0");
if (color.a < 255) {
const a = color.a.toString(16).padStart(2, "0");
return `#${r}${g}${b}${a}`;
}
return `#${r}${g}${b}`;
}
/**
* Parse color input (string or RGBA object) to ParsedColor
*/
static parseColorInput(input) {
if (typeof input === "object") {
return {
r: Math.max(0, Math.min(255, input.r)),
g: Math.max(0, Math.min(255, input.g)),
b: Math.max(0, Math.min(255, input.b)),
a: input.a !== undefined ? Math.max(0, Math.min(255, input.a)) : 255,
};
}
// Use ModelDesignUtilities.parseColor for string parsing
const parsed = ModelDesignUtilities_1.default.parseColor(input);
return {
r: parsed.r,
g: parsed.g,
b: parsed.b,
a: parsed.a ?? 255,
};
}
}
exports.default = NoiseGenerationUtilities;