const canvas = document.getElementById('canvas');
const c = canvas.getContext('2d');
canvas.width = innerWidth;
canvas.height = innerHeight;
function mult(arr, val){
if(arguments.length === 1){
let sum = 1;
for(let i = 0; i < arr.length; i++){
sum *= arr[i];
}
return sum;
}else{
let sum = 1;
for(let i = 0; i < arr.length; i++){
sum += arr[i] * val;
}
return sum;
}
}
//1d interpolation
function interpolate(arr, t){
let a = arr[0];
let b = arr[1];
let c = arr[2];
let d = arr[3];
return 0.5 * (c - a + (2.0*a - 5.0*b + 4.0*c - d + (3.0*(b - c) + d - a)*t)*t)*t + b;
}
//nd interpolation
function Interpolate (n, arr, coordinates) {
if(n === 1){
return interpolate(arr, coordinates[0]);
}else{
let temp = [];
let cords = [...coordinates].slice(1)
temp[0] = Interpolate(n-1, arr[0],cords);
temp[1] = Interpolate(n-1, arr[1],cords);
temp[2] = Interpolate(n-1, arr[2],cords);
temp[3] = Interpolate(n-1, arr[3],cords);
return interpolate(temp, coordinates[0])
}
}
//psudo random number generator
function rand(seed, ...prams){
for(let i in prams){
prams[i] *= mult(prams) + seed;
prams[i] ^= prams[i] << 13;
prams[i] ^= prams[i] >> 17;
prams[i] ^= prams[i] << 5;
}
return (Math.sin(prams.reduce((total, num) => total + num)) + 1) / 2;
}
//generates a nD array of random values
function NDrandArr(n, seed, len, starts, incs, cords = []){
if(n == 1){
let a = [];
for(let i = 0; i < len; i++){
let p = cords.slice(0);
p.push(i)
for(let i in p){
p[i] *= incs[i];
p[i] += starts[i];
}
a.push(rand(seed, ...p));
}
return a;
}else{
let a = [];
for(let i = 0; i < len; i++){
let p = cords.slice(0);
p.push(i)
a[i] = NDrandArr(n-1, seed, len, starts, incs, p)
}
return a;
}
}
//noise class
class Noise{
constructor(seed){
this.seed = seed;
this.octives = [];
this.totalPow = 0;
}
addOctive(time, pow){
this.octives.push({
time: time,
pow: pow
});
this.totalPow += pow;
}
getVal(...prams){
//IMPORTANT, ACUALY RETURNS VALUES
let sum = 0;
for(let i = 0; i < this.octives.length; i++){
let bottoms = prams.map(item => Math.floor(item / this.octives[i].time)-1);
let dim = [];
let times = [];
for(let j in prams){
let over = prams[j] % this.octives[i].time;
dim[j] = over / this.octives[i].time;
times[j] = this.octives[i].time;
}
let a = NDrandArr(prams.length, this.seed, 4, bottoms, times);
let I = Interpolate(prams.length, a, dim);
sum += I * this.octives[i].pow;
}
//put between 0 and 1
return sum / this.totalPow;
}
}
//just adds octives to noise
function addNoiseHalfSteps(noise, numsteps, startPow, startFr){
for(let i = 0; i< numsteps; i++){
noise.addOctive(startFr/ (2**i), startPow / (2**i))
}
}
//noise object
const noise = new Noise(43323);
addNoiseHalfSteps(noise, 4, 1, 32);
//function to display
function draw(){
for(let i = 0; i < innerWidth; i+= innerWidth/200){
for(let j = 0; j < innerHeight; j += innerHeight/200){
let bright = noise.getVal(i,j)*255;
c.fillStyle = 'rgb('+bright+','+bright+','+bright+')';
c.beginPath();
c.fillRect(i,j, innerWidth/200, innerHeight/200);
};
}
}
draw();
<!DOCTYPE html>
<html>
<head>
<style>
</style>
</head>
<body>
<canvas id = 'canvas'></canvas>
</body>
</html>
Я работал над генератором шума, похожим на perlin, для javascript игр с намерением заставить его работать в любом измерении. Чтобы сделать его более плавным, я использовал интерполяцию cubi c, формулы для которой я нашел здесь . Чтобы получить значение, я беру 4 значения (2 впереди и 2 позади) и генерирую псевдослучайное число для каждого расположение. Затем я интерполирую для неизвестного и возвращаю это значение. Я делаю это много раз, каждый раз вдвое уменьшая диапазон и расстояние случайных чисел. Это, очевидно, упрощено, но работает и было протестировано до 4d. Проблема в том, что это не выглядит непрерывным. Он разделен на блоки, которые сами разбиты на более мелкие блоки.
Код JS: (извините за то, что это так долго, я обрезал столько, сколько я мог)
const canvas = document.getElementById('canvas');
const c = canvas.getContext('2d');
canvas.width = innerWidth;
canvas.height = innerHeight;
function mult(arr, val){
if(arguments.length === 1){
let sum = 1;
for(let i = 0; i < arr.length; i++){
sum *= arr[i];
}
return sum;
}else{
let sum = 1;
for(let i = 0; i < arr.length; i++){
sum += arr[i] * val;
}
return sum;
}
}
//1d interpolation
function interpolate(arr, t){
let a = arr[0];
let b = arr[1];
let c = arr[2];
let d = arr[3];
return 0.5 * (c - a + (2.0*a - 5.0*b + 4.0*c - d + (3.0*(b - c) + d - a)*t)*t)*t + b;
}
//nd interpolation
function Interpolate (n, arr, coordinates) {
if(n === 1){
return interpolate(arr, coordinates[0]);
}else{
let temp = [];
let cords = [...coordinates].slice(1)
temp[0] = Interpolate(n-1, arr[0],cords);
temp[1] = Interpolate(n-1, arr[1],cords);
temp[2] = Interpolate(n-1, arr[2],cords);
temp[3] = Interpolate(n-1, arr[3],cords);
return interpolate(temp, coordinates[0])
}
}
//psudo random number generator
function rand(seed, ...prams){
for(let i in prams){
prams[i] *= mult(prams) + seed;
prams[i] ^= prams[i] << 13;
prams[i] ^= prams[i] >> 17;
prams[i] ^= prams[i] << 5;
}
return (Math.sin(prams.reduce((total, num) => total + num)) + 1) / 2;
}
//generates a nD array of random values
function NDrandArr(n, seed, len, starts, incs, cords = []){
if(n == 1){
let a = [];
for(let i = 0; i < len; i++){
let p = cords.slice(0);
p.push(i)
for(let i in p){
p[i] *= incs[i];
p[i] += starts[i];
}
a.push(rand(seed, ...p));
}
return a;
}else{
let a = [];
for(let i = 0; i < len; i++){
let p = cords.slice(0);
p.push(i)
a[i] = NDrandArr(n-1, seed, len, starts, incs, p)
}
return a;
}
}
//noise class
class Noise{
constructor(seed){
this.seed = seed;
this.octaves = [];
this.totalPow = 0;
}
addOctave(time, pow){
this.octaves.push({
time: time,
pow: pow
});
this.totalPow += pow;
}
getVal(...prams){
//IMPORTANT, ACTUALLY RETURNS VALUES
let sum = 0;
for(let i = 0; i < this.octaves.length; i++){
let bottoms = prams.map(item => Math.floor(item / this.octaves[i].time)-1);
let dim = [];
let times = [];
for(let j in prams){
let over = prams[j] % this.octaves[i].time;
dim[j] = over / this.octaves[i].time;
times[j] = this.octaves[i].time;
}
let a = NDrandArr(prams.length, this.seed, 4, bottoms, times);
let I = Interpolate(prams.length, a, dim);
sum += I * this.octaves[i].pow;
}
//put between 0 and 1
return sum / this.totalPow;
}
}
//just adds octives to noise
function addNoiseHalfSteps(noise, numsteps, startPow, startFr){
for(let i = 0; i< numsteps; i++){
noise.addOctave(startFr/ (2**i), startPow / (2**i))
}
}
//noise object
const noise = new Noise(43323);
addNoiseHalfSteps(noise, 4, 1, 32);
//function to display
function draw(){
for(let i = 0; i < innerWidth; i+= innerWidth/20){
for(let j = 0; j < innerHeight; j += innerHeight/20){
let bright = noise.getVal(i,j)*255;
c.fillStyle = 'rgb('+bright+','+bright+','+bright+')';
c.beginPath();
c.fillRect(i,j, innerWidth/20, innerHeight/20);
};
}
}
draw();
У меня есть две теории об этом:
- Интерполяция не является непрерывной, и резко меняет то, что входы сдвинуты только на 1. Это объяснило бы все промежутки, они являются границами между различными входными значениями, которые используются для расчета шума
2: это слишком много, и мне нужно увеличить частоты, что сделало бы эти «блоки» намного меньше, но сделать его более случайным
Я думаю, что это больше связано с первой теорией, чем второй, но ни один из моих поисков в Google для непрерывной интерполяции не дал ничего полезного, чего я еще не пробовал.
Если это из-за одного из них, что будет лучшим способом решения этих проблем, и если нет, то что вызывает эти дискретные «остановки» и где я должен искать ответы?