const formatMessage = require('format-message'); const BlockType = require('../../extension-support/block-type'); const ArgumentType = require('../../extension-support/argument-type'); const MersenneTwister = require('mersenne-twister'); const { createNoise3D } = require('simplex-noise'); /** * Class for perlin noise extension. * @constructor */ // noise generation code from p5.js class iygPerlin { constructor(runtime) { /** * The runtime instantiating this block package. * @type {Runtime} */ this.runtime = runtime; this.noise; this.seed = 123; this.size = 50; this.generator = new MersenneTwister(this.seed); } /** * @returns {object} metadata for this extension and its blocks. */ getInfo() { return { id: 'iygPerlin', name: 'Perlin Noise', color1: '#525252', color2: '#636363', blocks: [ // Hidden { opcode: 'GetNoise', blockType: BlockType.REPORTER, text: formatMessage({ id: 'iygPerlin.GetNoise', default: 'Get perlin noise with seed [SEED] and octave [OCTAVE] at x [X], y [Y], and z [Z]', description: 'Get seeded perlin noise at a specified x and y and z.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, OCTAVE: { type: ArgumentType.NUMBER, defaultValue: 4 }, X: { type: ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: ArgumentType.NUMBER, defaultValue: 0 }, Z: { type: ArgumentType.NUMBER, defaultValue: 0 } }, hideFromPalette: true }, // Hidden { opcode: 'GetRandomNoise', blockType: BlockType.REPORTER, text: formatMessage({ id: 'iygPerlin.GetRandomNoise', default: 'Get noise with seed [SEED] at x [X], y [Y], and z [Z]', description: 'Get seeded noise with a specified seed at a specified x and y and z.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, X: { type: ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: ArgumentType.NUMBER, defaultValue: 0 }, Z: { type: ArgumentType.NUMBER, defaultValue: 0 } }, hideFromPalette: true }, // Hidden { opcode: 'GeneratePerlinNoise', blockType: BlockType.COMMAND, text: formatMessage({ id: 'iygPerlin.GeneratePerlinNoise', default: 'Pre-generate perlin noise with seed [SEED] and octave [OCTAVE]', description: 'Pre-generate seeded perlin noise.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, OCTAVE: { type: ArgumentType.NUMBER, defaultValue: 4 } }, hideFromPalette: true }, // Hidden { opcode: 'GenerateRandomNoise', blockType: BlockType.COMMAND, hideFromPalette: true, text: formatMessage({ id: 'iygPerlin.GenerateRandomNoise', default: 'not needed [SEED] [SIZE]', description: 'Pre-generate seeded noise.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, SIZE: { type: ArgumentType.NUMBER, defaultValue: 50 } }, }, // Hidden { opcode: 'getSimplexNoise', blockType: BlockType.REPORTER, hideFromPalette: true, text: formatMessage({ id: 'iygPerlin.getSimplexNoise', default: 'Get simplex noise with seed [SEED] at x [X], y [Y], and z [Z]', description: 'Get simplex noise with a specified seed at a specified x and y and z.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, X: { type: ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: ArgumentType.NUMBER, defaultValue: 0 }, Z: { type: ArgumentType.NUMBER, defaultValue: 0 } } }, // End of hidden stuff { opcode: 'GetNoiseV2', blockType: BlockType.REPORTER, text: formatMessage({ id: 'iygPerlin.GetNoiseV2', default: 'Get perlin noise with seed [SEED] and octave [OCTAVE] at x [X], y [Y], and z [Z]', description: 'Get seeded perlin noise at a specified x and y and z.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, OCTAVE: { type: ArgumentType.NUMBER, defaultValue: 4 }, X: { type: ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: ArgumentType.NUMBER, defaultValue: 0 }, Z: { type: ArgumentType.NUMBER, defaultValue: 0 } }, }, { opcode: 'GetRandomNoiseV2', blockType: BlockType.REPORTER, text: formatMessage({ id: 'iygPerlin.GetRandomNoiseV2', default: 'Get random noise with seed [SEED] at x [X], y [Y], and z [Z]', description: 'Get seeded random noise with a specified seed at a specified x and y and z.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, X: { type: ArgumentType.NUMBER, defaultValue: 0 }, Y: { type: ArgumentType.NUMBER, defaultValue: 0 }, Z: { type: ArgumentType.NUMBER, defaultValue: 0 } } }, { opcode: 'GeneratePerlinNoiseV2', blockType: BlockType.COMMAND, text: formatMessage({ id: 'iygPerlin.GeneratePerlinNoiseV2', default: 'Pre-generate perlin noise with seed [SEED] and octave [OCTAVE]', description: 'Pre-generate seeded perlin noise.' }), arguments: { SEED: { type: ArgumentType.NUMBER, defaultValue: 123 }, OCTAVE: { type: ArgumentType.NUMBER, defaultValue: 4 } } }, ] }; } goodSeedRandom() { this.generator.init_seed(this.seed); let result = this.generator.random_incl(); this.seed = result * 4294967296; return result; } dumbSeedRandom() { this.generator.init_seed(this.seed); let result = this.generator.random_incl(); this.seed = (1664525*this.seed + 1013904223) % 4294967296; return result; } GeneratePerlinNoise(args, util) { args.X = 0; args.Y = 0; args.Z = 0; this.GetNoise(args, util); } GenerateRandomNoise(args, util) { let seed = args.SEED; let size = args.SIZE; if (this.noise == null || seed != this.seed) { this.noise = new Array(size); this.seed = seed; for (let i = 0; i < size; i++) { this.noise[i] = new Array(size); for (let j = 0; j < size; j++) { this.noise[i][j] = new Array(size); for (let k = 0; k < size; k++) { this.noise[i][j][k] = this.dumbSeedRandom(); } } } this.seed = seed; this.prev_seed = seed; this.size = size; } if (size > this.size && seed == this.seed) { this.seed = this.prev_seed; for (let i = this.size; i < size+1; i++) { this.noise[i] = new Array(size); for (let j = this.size; j < size+1; j++) { this.noise[i][j] = new Array(size); for (let k = this.size; k < size+1; k++) { this.noise[i][j][k] = this.dumbSeedRandom(); } } } } } GetRandomNoise(args, util) { let seed = args.SEED; let x = args.X; let y = args.Y; let z = args.Z; let pre_seed = this.seed; this.seed = seed+x+y*1000+z*10000; let result = this.dumbSeedRandom(); this.seed = pre_seed; return result; } generatePerlin(seed, perlin_octaves, rand, x, y, z) { let perlin_amp_falloff = 0.5; const scaled_cosine = i => 0.5 * (1.0 - Math.cos(i * Math.PI)); const PERLIN_SIZE = 4095; const PERLIN_YWRAPB = 4; const PERLIN_YWRAP = 1 << PERLIN_YWRAPB; const PERLIN_ZWRAPB = 8; const PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB; if (this.perlin == null || seed != this.seed) { this.perlin = new Array(PERLIN_SIZE + 1); this.seed = seed; for (let i = 0; i < PERLIN_SIZE + 1; i++) { this.perlin[i] = rand(); } this.seed = seed; } if (x < 0) { x = -x; } if (y < 0) { y = -y; } if (z < 0) { z = -z; } let xi = Math.floor(x), yi = Math.floor(y), zi = Math.floor(z); let xf = x - xi; let yf = y - yi; let zf = z - zi; let rxf, ryf; let r = 0; let ampl = 0.5; let n1, n2, n3; for (let o = 0; o < perlin_octaves; o++) { let of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB); rxf = scaled_cosine(xf); ryf = scaled_cosine(yf); n1 = this.perlin[of & PERLIN_SIZE]; n1 += rxf * (this.perlin[(of + 1) & PERLIN_SIZE] - n1); n2 = this.perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE]; n2 += rxf * (this.perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n2); n1 += ryf * (n2 - n1); of += PERLIN_ZWRAP; n2 = this.perlin[of & PERLIN_SIZE]; n2 += rxf * (this.perlin[(of + 1) & PERLIN_SIZE] - n2); n3 = this.perlin[(of + PERLIN_YWRAP) & PERLIN_SIZE]; n3 += rxf * (this.perlin[(of + PERLIN_YWRAP + 1) & PERLIN_SIZE] - n3); n2 += ryf * (n3 - n2); n1 += scaled_cosine(zf) * (n2 - n1); r += n1 * ampl; ampl *= perlin_amp_falloff; xi <<= 1; xf *= 2; yi <<= 1; yf *= 2; zi <<= 1; zf *= 2; if (xf >= 1.0) { xi++; xf--; } if (yf >= 1.0) { yi++; yf--; } if (zf >= 1.0) { zi++; zf--; } } return r % 1.0; } GetNoise(args, util) { let seed = args.SEED; let perlin_octaves = ((args.OCTAVE === Infinity) ? 4 : args.OCTAVE); let x = args.X + .5; let y = args.Y + .5; let z = args.Z + .5; return this.generatePerlin(seed, perlin_octaves, this.dumbSeedRandom.bind(this), x, y, z); } // ----- V2 ----- GetNoiseV2(args, util) { let seed = args.SEED; let perlin_octaves = ((args.OCTAVE === Infinity) ? 4 : args.OCTAVE); let x = args.X + .5; let y = args.Y + .5; let z = args.Z + .5; return this.generatePerlin(seed, perlin_octaves, this.goodSeedRandom.bind(this), x, y, z); } // ----- V2 ----- GetRandomNoiseV2(args, util) { let seed = args.SEED; let x = args.X; let y = args.Y; let z = args.Z; let pre_seed = this.seed; this.seed = seed + (x * 743) + (y * 942 ) + (z * 645); let result = this.goodSeedRandom.bind(this)(); this.seed = pre_seed; return result; } // ----- V2 ----- GeneratePerlinNoiseV2(args, util) { args.X = 0; args.Y = 0; args.Z = 0; this.GetNoiseV2(args, util); } getSimplexNoise(args) { const seed = args.SEED; const x = args.X; const y = args.Y; const z = args.Z; this.generator.init_seed(seed); const noise = createNoise3D(this.generator.random_incl.bind(this.generator)); return noise(x, y, z); } } module.exports = iygPerlin;