0

I want to simulate a point that moves with random vibration around a mean position (let's say around the position [X, Y, Z] = [0,0,0]). The first solution that I found is to sum a couple of sinusoids for each axis based on the following equation:

<a href="https://www.codecogs.com/eqnedit.php?latex=\sum_{i&space;=&space;1}^n&space;A_i&space;\sin(\omega_i&space;t&plus;\phi)" target="_blank"><img src="https://latex.codecogs.com/gif.latex?\sum_{i&space;=&space;1}^n&space;A_i&space;\sin(\omega_i&space;t&plus;\phi)" title="\sum_{i = 1}^n A_i \sin(\omega_i t+\phi)" /></a>

where A_i is a normal random amplitude, and omega_i is a normal random frequency. I have not tested the phase yet, so I leave it to zero for now. I generated figures of the expect normal distribution and equation results with the following approach. I tried multiple values of N and I'm not sure that the equation is giving a normally distributed results. Is my approach correct? Is there a better way to generate random vibration?

m_power
  • 3,156
  • 5
  • 33
  • 54
  • Why do you need a sinusoid? just add a random offset in each direction for each vibration. And, if you are using normally distributed random numbers, the results histogram will be normally distributed and have the expected Gaussian "bell shaped" curve – Eliahu Aaron Jun 13 '19 at 17:06
  • @EliahuAaron I want the point to have a smooth motion. – m_power Jun 13 '19 at 17:22
  • 2
    Random movement is not "smooth", its "jittery". Maybe you want to move the point in some "smooth" path an add a small random vibration to it. If so, you can chose a sinusoid path in each direction and **add** to it some small random noise – Eliahu Aaron Jun 13 '19 at 17:40
  • @EliahuAaron If a Gaussian approximation is possible with a sums of sines (or something else), I could create smooth motion. – m_power Jun 13 '19 at 18:30
  • @EliahuAaron, A simple example would be the height of ocean wave. I'm guessing the height would be relativly smooth (i.e., there is no jitter on a boat) and that it could be approximated by some random distribution. – m_power Jun 14 '19 at 14:20

1 Answers1

2

For such a task, you may find useful Perlin Noise or even Fractal Brownian Motion noise. See this implementation in JavaScript:

class Utils {
    static Lerp(a, b, t) {
        return (1 - t) * a + t * b;
    }

    static Fade(t) {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }   
}

class Noise {
    constructor() {
        this.p = [];
        this.permutationTable = [];
        this.grad3 = [[1, 1, 0], [-1, 1, 0], [1, -1, 0], 
        [-1, -1, 0], [1, 0, 1], [-1, 0, 1], 
        [1, 0, -1], [-1, 0, -1], [0, 1, 1], 
        [0, -1, 1], [0, 1, -1], [0, -1, -1]];

        for (let i = 0; i < 256; i++)
            this.p[i] = Math.floor(Math.random() * 256);

        for (let i = 0; i < 512; i++)
            this.permutationTable[i] = this.p[i & 255];
    }

    PerlinDot(g, x, y, z) {
        return g[0] * x + g[1] * y + g[2] * z;
    }             

    PerlinNoise(x, y, z) {
        let a = Math.floor(x);
        let b = Math.floor(y);
        let c = Math.floor(z);

        x = x - a;
        y = y - b;
        z = z - c;

        a &= 255;
        b &= 255;
        c &= 255;

        let gi000 = this.permutationTable[a + this.permutationTable[b + this.permutationTable[c]]] % 12;
        let gi001 = this.permutationTable[a + this.permutationTable[b + this.permutationTable[c + 1]]] % 12;
        let gi010 = this.permutationTable[a + this.permutationTable[b + 1 + this.permutationTable[c]]] % 12;
        let gi011 = this.permutationTable[a + this.permutationTable[b + 1 + this.permutationTable[c + 1]]] % 12;
        let gi100 = this.permutationTable[a + 1 + this.permutationTable[b + this.permutationTable[c]]] % 12;
        let gi101 = this.permutationTable[a + 1 + this.permutationTable[b + this.permutationTable[c + 1]]] % 12;
        let gi110 = this.permutationTable[a + 1 + this.permutationTable[b + 1 + this.permutationTable[c]]] % 12;
        let gi111 = this.permutationTable[a + 1 + this.permutationTable[b + 1 + this.permutationTable[c + 1]]] % 12;

        let n000 = this.PerlinDot(this.grad3[gi000], x, y, z);
        let n100 = this.PerlinDot(this.grad3[gi100], x - 1, y, z);
        let n010 = this.PerlinDot(this.grad3[gi010], x, y - 1, z);
        let n110 = this.PerlinDot(this.grad3[gi110], x - 1, y - 1, z);
        let n001 = this.PerlinDot(this.grad3[gi001], x, y, z - 1);
        let n101 = this.PerlinDot(this.grad3[gi101], x - 1, y, z - 1);
        let n011 = this.PerlinDot(this.grad3[gi011], x, y - 1, z - 1);
        let n111 = this.PerlinDot(this.grad3[gi111], x - 1, y - 1, z - 1);

        let u = Utils.Fade(x);
        let v = Utils.Fade(y);
        let w = Utils.Fade(z);

        let nx00 = Utils.Lerp(n000, n100, u);
        let nx01 = Utils.Lerp(n001, n101, u);
        let nx10 = Utils.Lerp(n010, n110, u);
        let nx11 = Utils.Lerp(n011, n111, u);

        let nxy0 = Utils.Lerp(nx00, nx10, v);
        let nxy1 = Utils.Lerp(nx01, nx11, v);

        return Utils.Lerp(nxy0, nxy1, w);
    }

    FractalBrownianMotion(x, y, z, octaves, persistence) {
        let total = 0;
        let frequency = 1;
        let amplitude = 1;
        let maxValue = 0;

        for(let i = 0; i < octaves; i++) {
            total = this.PerlinNoise(x * frequency, y * frequency, z * frequency) * amplitude;
            maxValue += amplitude;
            amplitude *= persistence;
            frequency *= 2;
        }

        return total / maxValue;
    }
} 

With Fractal Brownian Motion can have huge control about the randomness of distribution. You can set the scale, initial offset and its increment for each axis, octaves, and persistence. You can generate as many positions you like by incrementing the offsets, like this:

const NUMBER_OF_POSITIONS = 1000;
const X_OFFSET = 0;
const Y_OFFSET = 0;
const Z_OFFSET = 0;
const X_SCALE = 0.01;
const Y_SCALE = 0.01;
const Z_SCALE = 0.01;
const OCTAVES = 8;
const PERSISTENCE = 2;
const T_INCREMENT = 0.1;
const U_INCREMENT = 0.01;
const V_INCREMENT = 1;

let noise = new Noise();
let positions = [];

let i = 0, t = 0, u = 0, v = 0;
while(i <= NUMBER_OF_POSITIONS) {
    let position = {x:0, y:0, z:0};

    position.x = noise.FractalBrownianMotion((X_OFFSET + t) * X_SCALE, (Y_OFFSET + t) * Y_SCALE, (Z_OFFSET + t) * Z_SCALE, OCTAVES, PERSISTENCE); 
    position.y = noise.FractalBrownianMotion((X_OFFSET + u) * X_SCALE, (Y_OFFSET + u) * Y_SCALE, (Z_OFFSET + u) * Z_SCALE, OCTAVES, PERSISTENCE); 
    position.z = noise.FractalBrownianMotion((X_OFFSET + v) * X_SCALE, (Y_OFFSET + v) * Y_SCALE, (Z_OFFSET + v) * Z_SCALE, OCTAVES, PERSISTENCE); 
    positions.push(position);

    t += T_INCREMENT;
    u += U_INCREMENT;
    v += V_INCREMENT;
    i++;
}

Positions you get with these options would look similar to these:

...
501: {x: 0.0037344935483775883, y: 0.1477509219864437, z: 0.2434570202517206}
502: {x: -0.008955635460317357, y: 0.14436114483299245, z: -0.20921147024725012}
503: {x: -0.06021806450587406, y: 0.14101769272762685, z: 0.17093922757597568}
504: {x: -0.05796055906294283, y: 0.13772732578136435, z: 0.0018755951606465138}
505: {x: 0.02243901814464688, y: 0.13448621540816477, z: 0.013341084536334057}
506: {x: 0.05074194554980439, y: 0.1312810723109357, z: 0.15821600463130164}
507: {x: 0.011075140752144507, y: 0.12809058766450473, z: 0.04006055269090941}
508: {x: -0.0000031848272303249632, y: 0.12488712875549206, z: -0.003957905411646261}
509: {x: -0.0029798194097060307, y: 0.12163862278870072, z: -0.1988934273517602}
510: {x: -0.008762098499026483, y: 0.11831055728747841, z: 0.02222898347134993}
511: {x: 0.01980289423585394, y: 0.11486802263767962, z: -0.0792283303765883}
512: {x: 0.0776034130079849, y: 0.11127772191732693, z: -0.14141576745502138}
513: {x: 0.08695806478169149, y: 0.10750987521108693, z: 0.049654228704645}
514: {x: 0.036915612100698, y: 0.10353995005320946, z: 0.00033977899920740567}
515: {x: 0.0025923223158845687, y: 0.09935015632822117, z: -0.00952549797548823}
516: {x: 0.0015456084571764527, y: 0.09493065267319889, z: 0.12609905321632175}
517: {x: 0.0582996941155056, y: 0.09028042189611517, z: -0.27532974820612816}
518: {x: 0.19186052966982514, y: 0.08540778482478142, z: -0.00035058098387404606}
519: {x: 0.27063961068049447, y: 0.08033053495775729, z: -0.07737309686568927}
520: {x: 0.20318957178662056, y: 0.07507568989311474, z: -0.14633819135757353}
...

Note: for efficiency, it's a good idea generate all positions only once into an array of positions like in this example, and then in some animation loop just assigning positions to your point from this array one by one.

Bonus: Here you can see how those values affect the distribution of multiple points by playing around with real-time response control panel: https://marianpekar.github.io/fbm-space/

References:

https://en.wikipedia.org/wiki/Fractional_Brownian_motion

https://en.wikipedia.org/wiki/Perlin_noise

Marian Pekár
  • 41
  • 1
  • 5