Cubi c Сплайн-интерполяция появляется не непрерывно - PullRequest
0 голосов
/ 14 апреля 2020

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. Проблема в том, что это не выглядит непрерывным. Он разделен на блоки, которые сами разбиты на более мелкие блоки. enter image description here

Код 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. Интерполяция не является непрерывной, и резко меняет то, что входы сдвинуты только на 1. Это объяснило бы все промежутки, они являются границами между различными входными значениями, которые используются для расчета шума

2: это слишком много, и мне нужно увеличить частоты, что сделало бы эти «блоки» намного меньше, но сделать его более случайным

Я думаю, что это больше связано с первой теорией, чем второй, но ни один из моих поисков в Google для непрерывной интерполяции не дал ничего полезного, чего я еще не пробовал.

Если это из-за одного из них, что будет лучшим способом решения этих проблем, и если нет, то что вызывает эти дискретные «остановки» и где я должен искать ответы?

...