Optimizing a 3D World Javascript Animation - javascript

I've recently come up with the idea to create a tag cloud like animation shaped like the earth. I've extracted the coastline coordinates from ngdc.noaa.gov and wrote a little script that displayed it in my browser. Now as you can imagine, the whole coastline consists of about 48919 points, which my script would individually render (each coordinate being represented by one span). Obviously no browser is capable of rendering this fluently - but it would be nice if I could render as much as let's say 200 spans (twice as much as now) on my old p4 2.8 Ghz (as a representative benchmark). Are there any javascript optimizations I could use in order to speed up the display of those spans?
One 'coordinate':
<div id="world_pixels">
<span id="wp_0" style="position:fixed; top:0px; left:0px; z-index:1; font-size:20px; cursor:pointer;cursor:hand;" onmouseover="magnify_world_pixel('wp_0');" onmouseout="shrink_world_pixel('wp_0');" onClick="set_askcue_bar('', 'new york')">new york</span>
</div>
The script:
$(document).ready(function(){
world_pixels = $("#world_pixels span");
world_pixels.spin();
setInterval("world_pixels.spin()",1500);
});
z = new Array();
$.fn.spin = function () {
for(i=0; i<this.length; i++) {
/*actual screen coordinates: x/y/z --> left/font-size/top
300/13/0 300/6/300
| /
|/
0/13/300 ----|---- 600/13/300
/|
/ |
300/20/300 300/13/600
*/
/*scale font size*/
var resize_x = 1;
/*scale width*/
var resize_y = 2.5;
/*scale height*/
var resize_z = 2.5;
var from_left = 300;
var from_top = 20;
/*actual math coordinates:
1 -1
| /
|/
1 ----|---- -1
/|
/ |
1 -1
*/
//var get_element = document.getElementById();
//var font_size = parseInt(this.style.fontSize);
var font_size = parseInt($(this[i]).css("font-size"));
var left = parseInt($(this[i]).css("left"));
if (coast_line_array[i][1]) {
} else {
var top = parseInt($(this[i]).css("top"));
z[i] = from_top + (top - (300 * resize_z)) / (300 * resize_z);
//global beacause it's used in other functions later on
var top_new = from_top + Math.round(Math.cos(coast_line_array[i][2]/90*Math.PI) * (300 * resize_z) + (300 * resize_z));
$(this[i]).css("top", top_new);
coast_line_array[i][3] = 1;
}
var x = resize_x * (font_size - 13) / 7;
var y = from_left + (left- (300 * resize_y)) / (300 * resize_y);
if (y >= 0) {
this[i].phi = Math.acos(x/(Math.sqrt(x^2 + y^2)));
} else {
this[i].phi = 2*Math.PI - Math.acos(x/(Math.sqrt(x^2 + y^2)));
i
}
this[i].theta = Math.acos(z[i]/Math.sqrt(x^2 + y^2 + z[i]^2));
var font_size_new = resize_x * Math.round(Math.sin(coast_line_array[i][4]/90*Math.PI) * Math.cos(coast_line_array[i][0]/180*Math.PI) * 7 + 13);
var left_new = from_left + Math.round(Math.sin(coast_line_array[i][5]/90*Math.PI) * Math.sin(coast_line_array[i][0]/180*Math.PI) * (300 * resize_y) + (300 * resize_y));
//coast_line_array[i][6] = coast_line_array[i][7]+1;
if ((coast_line_array[i][0] + 1) > 180) {
coast_line_array[i][0] = -180;
} else {
coast_line_array[i][0] = coast_line_array[i][0] + 0.25;
}
$(this[i]).css("font-size", font_size_new);
$(this[i]).css("left", left_new);
}
}
resize_x = 1;
function magnify_world_pixel(element) {
$("#"+element).animate({
fontSize: resize_x*30+"px"
}, {
duration: 1000
});
}
function shrink_world_pixel(element) {
$("#"+element).animate({
fontSize: resize_x*6+"px"
}, {
duration: 1000
});
}
I'd appreciate any suggestions to optimize my script, maybe there is even a totally different approach on how to go about this.
The whole .js file which stores the array for all the coordinates is available on my page, the file is about 2.9 mb, so you might consider pulling the .zip for local testing:
metaroulette.com/files/31218.zip
metaroulette.com/files/31218.js
P.S. the php I use to create the spans:
<?php
//$arbitrary_characters = array('a','b','c','ddsfsdfsdf','e','f','g','h','isdfsdffd','j','k','l','mfdgcvbcvbs','n','o','p','q','r','s','t','uasdfsdf','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9',);
$arbitrary_characters = array('cat','table','cool','deloitte','askcue','what','more','less','adjective','nice','clinton','mars','jupiter','testversion','beta','hilarious','lolcatz','funny','obama','president','nice','what','misplaced','category','people','religion','global','skyscraper','new york','dubai','helsinki','volcano','iceland','peter','telephone','internet', 'dialer', 'cord', 'movie', 'party', 'chris', 'guitar', 'bentley', 'ford', 'ferrari', 'etc', 'de facto');
for ($i=0; $i<96; $i++) {
$arb_digits = rand (0,45);
$arbitrary_character = $arbitrary_characters[$arb_digits];
//$arbitrary_character = ".";
echo "<span id=\"wp_$i\" style=\"position:fixed; top:0px; left:0px; z-index:1; font-size:20px; cursor:pointer;cursor:hand;\" onmouseover=\"magnify_world_pixel('wp_$i');\" onmouseout=\"shrink_world_pixel('wp_$i');\" onClick=\"set_askcue_bar('', '$arbitrary_character')\">$arbitrary_character</span>\n";
}
?>

You could always use the <canvas> element. It renders much more quickly on browsers that support it.
You'll have to use a workaround for Internet Explorer, though, until version 9 comes out. You can use ExplorerCanvas to emulate canvas support for IE. However, just know that it's very slow--possibly even slower than your algorithm. If IE support is important to you, you could ask users to install Google Chrome Frame if they want a better experience while still using the Internet Explorer browser; but other than that, there isn't much you can do to speed this kind of thing up in IE.

Well I see one easy optimization:
for(i=0;i<this.length; i++) {
You are checking this.length each time this loop iterates. This is expensive, and unless you expect the length to change, unnecessary.
Try:
for(i=0,ii=this.length;i<ii; i++) {
instead.
I don't do much speed-crucial work in JS, so I can only offer some more tentative suggestions.
You can possibly move some of the heavy calculation to a WebWorker, but I'm not sure if your situation would benefit much from it.
Check them out here: https://developer.mozilla.org/En/Using_web_workers
Also, if you are not using any of the Array object's functions for an array, try using an object with integer "keys" instead.
I've never personally done a benchmark on object vs array lookups, but I have friends who insist that objects are much faster in some cases (even though, in theory, they should be comparable).
It's a very quick and easy mod of your code, so why not try it?

Related

Calculate/replicate RSI from Tradingview's pine script

Im trying to recreate Tradingviews pine script RSI code into Javascript code. But having a hard time figuring out how it works. I made the basic RSI using a normal moving average calculation. But the pine script uses exponential weighted moving average. And there documentation is really hard to follow to me. This is the pine script.
//#version=4
study(title="Relative Strength Index", shorttitle="RSI", format=format.price, precision=2, resolution="")
len = input(14, minval=1, title="Length")
src = input(close, "Source", type = input.source)
up = rma(max(change(src), 0), len)
down = rma(-min(change(src), 0), len)
rsi = down == 0 ? 100 : up == 0 ? 0 : 100 - (100 / (1 + up / down))
plot(rsi, "RSI", color=#7E57C2)
band1 = hline(70, "Upper Band", color=#787B86)
bandm = hline(50, "Middle Band", color=color.new(#787B86, 50))
band0 = hline(30, "Lower Band", color=#787B86)
fill(band1, band0, color=color.rgb(126, 87, 194, 90), title="Background")
This is what I oould make of it in Javascript:
// Period = 200
// Close variable is 200 closed values. Where [0] in array = oldest, [199] in array = newest value.
/**
* Relative strength index. Based on closed periods.
*
* #param {Array} close
* #param {Integer} period
* #returns
*/
function calculateRSI(close, period) {
// Only calculate if it is worth it. First {period - 1} amount of calculations aren't correct anyway.
if (close.length < period) {
return 50;
}
let averageGain = 0;
let averageLoss = 0;
const alpha = 1 / period;
// Exponential weighted moving average.
for (let i = 1; i < period; i++)
{
let change = close[i] - close[i - 1];
if (change >= 0) {
averageGain = alpha * change + (1 - alpha) * averageGain;
} else {
averageLoss = alpha * -change + (1 - alpha) * averageLoss;
}
}
// Tried this too, but seems to not really matter.
// To get an actual average.
// averageGain /= period;
// averageLoss /= period;
// Calculate relative strength index. Where it can only be between 0 and 100.
var rsi = 100 - (100 / (1 + (averageGain / averageLoss)));
return rsi;
}
The results this function gives on my chart is not too bad, but it just isn't the same as I have it in Tradingview. I belive im missing something that the pine script does and I don't.
Things I dont understand of the pine script:
When does it do a for loop? I don't see it in there functions. If they don't, how do they calculate the average for a period of longer than 2? You have to loop for that right?
How does the rma function work? This is their docs.
I might have too many questions on this, but I think if you show a somewhat working example in Javascript of the RSI calculation like they do. Then I can probably make sense of it.
Is my calculation in Javascript correct to the one in the pine script?

A DFT analysis in a low speed data sampling

I have some sample data of vibrations analysis from sensors installed on electrical motors. The sampling is made once or, at most, 3 times per day. The values can be expressed in g, gE or mm/s.
I’m developing a personal algorithm in JavaScript to process some samples and perform a DFT. It’s a simple code that uses brute force to process my results. I compared the results (real and imaginary parts) from JavaScript and from MATLAB results and they matched perfectly.
However, my sampling rate is very slow. Because of this, I have a lot of questions which I couldn’t find the answers on my searches:
Is it possible to apply a DFT analysis on a slow sampling data as this?
How can I determine the correct frequency scale for the X axis? It’s complicated for me because I don’t have an explicit Fs (sampling rate) value.
In my case, would it be interesting to apply some window function like Hanning Window (suitable for vibrations analyses)?
JavaScriptCode:
//Signal is a pure one-dimensional of real data (vibration values)
const fft = (signal) => {
const pi2 = 6.2832 //pi const
let inputLength = signal.length;
let Xre = new Array(inputLength); //DFT real part
let Xim = new Array(inputLength); //DFT imaginary part
let P = new Array(inputLength); //Power of spectrum
let M = new Array(inputLength); //Magnitude of spectrum
let angle = 2 * Math.PI / inputLength;
//Hann Window
signal = signal.map((x, index) => {
return x * 0.5 * (1 - Math.cos((2 * Math.PI * index) / (inputLength - 1)));
});
for (let k = 0; k < inputLength; ++k) { // For each output element
Xre[k] = 0; Xim[k] = 0;
for (let n = 0; n < inputLength; ++n) { // For each input element
Xre[k] += signal[n] * Math.cos(angle * k * n);
Xim[k] -= signal[n] * Math.sin(angle * k * n);
}
P[k] = Math.pow(Xre[k], 2) + Math.pow(Xim[k], 2);
M[k] = Math.sqrt(Math.pow(Xre[k], 2) + Math.pow(Xim[k], 2));
}
return { Xre: Xre, Xim: Xim, P: P, M: M.slice(0, Math.round((inputLength / 2) + 1)) };
}
The first figure shows the charts results (time domain on the left side and frequency domain on the right side).
The second figure shows a little bit of my data samples:
Obs.: I'm sorry for the writing. I'm still a beginner English student.
The frequency doesn't matter. A frequency as low as 1/day is just as fine as any other frequency. But consider the Nyquist-Shannon theorem.
This is problematic. You need a fix sampling frequency for a DFT. You could do interpolation as preprocessing. But better would be to do the sampling at fix times.

Javascript requestAnimationFrame spinner

After investigating all possible ways to create a lightweight and flexible spinner, I ended up using requestAnimationFrame which is quite brilliant. It basically does the same thing as CSS3 animation: perform calculations and hand the result off to the browser in order to sync repaint with screen redraw (typically at 60fps). While CSS3 transition and animation are suitable for very basic usage since there's only a transitionend event which may not fire under certain circumstances, requestAnimationFrame offers full control and you can perform multiple complex calculations perfectly in sync with screen redraw.
Would it make sense to excecute this code in a HTML5 worker ?
CSS
i.spinner {position:relative;display:inline-block;margin:20px}
i.bar {display:block;position:absolute;top:0;left:50%;height:inherit}
i.bar i {display:block;width:100%;height:29%;background:#000}
i.bar:nth-child(2) {transform:rotate(45deg);-webkit-Transform:rotate(45deg);-moz-Transform:rotate(45deg);-ms-Transform:rotate(45deg)}
i.bar:nth-child(3) {transform:rotate(90deg);-webkit-Transform:rotate(90deg);-moz-Transform:rotate(90deg);-ms-Transform:rotate(90deg)}
i.bar:nth-child(4) {transform:rotate(135deg);-webkit-Transform:rotate(135deg);-moz-Transform:rotate(135deg);-ms-Transform:rotate(135deg)}
i.bar:nth-child(5) {transform:rotate(180deg);-webkit-Transform:rotate(180deg);-moz-Transform:rotate(180deg);-ms-Transform:rotate(180deg)}
i.bar:nth-child(6) {transform:rotate(225deg);-webkit-Transform:rotate(225deg);-moz-Transform:rotate(225deg);-ms-Transform:rotate(225deg)}
i.bar:nth-child(7) {transform:rotate(270deg);-webkit-Transform:rotate(270deg);-moz-Transform:rotate(270deg);-ms-Transform:rotate(270deg)}
i.bar:nth-child(8) {transform:rotate(315deg);-webkit-Transform:rotate(315deg);-moz-Transform:rotate(315deg);-ms-Transform:rotate(315deg)}
JS
function buildspinner(size, invert) {
var color = '#000',
spinner = document.createElement('i'),
bar = document.createElement('i'),
hand = document.createElement('i'),
opacitymap = [0.8, 0.2, 0.2, 0.2, 0.2, 0.5, 0.6, 0.7],
nodemap = [];
if (invert) {color = '#fff'};
spinner.className = 'spinner';
spinner.style.cssText = 'width:' + size + 'px;height:' + size + 'px';
bar.className = 'bar';
bar.style.cssText = 'width:' + (size / 9) + 'px;height:' + size + 'px;margin-left:' + (-size / 18) + 'px';
hand.style.cssText = 'border-radius:' + size + 'px;background:' + color;
bar.appendChild(hand);
for (var j = 0; j < 8; j++) {
var clone = bar.cloneNode(true);
clone.style.opacity = opacitymap[j];
spinner.appendChild(clone);
nodemap.push(clone)
}
document.body.appendChild(spinner);
requestAnimationFrame(function(timestamp) {animatespinner(timestamp, timestamp, 125, opacitymap, nodemap, 0)})
}
function animatespinner(starttime, timestamp, duration, opacitymap, nodemap, counter) {
var progress = (timestamp - starttime) / duration;
counter++;
if (counter % 3 == 0) {
for (var j = 0; j < 8; j++) {
var next = j - 1;
if (next < 0) {
next = 7
};
nodemap[j].style.opacity = (opacitymap[j] + (opacitymap[next] - opacitymap[j]) * progress)
}
}
if (progress < 1) {
requestAnimationFrame(function(timestamp) {animatespinner(starttime, timestamp, 125, opacitymap, nodemap, counter)})
} else {
var rotatearray = opacitymap.pop();
opacitymap.unshift(rotatearray);
requestAnimationFrame(function(timestamp) {animatespinner(timestamp, timestamp, 125, opacitymap, nodemap, 0)})
}
}
The counter variable is used for throttling. You want the animation to be smooth, but you want to keep CPU usage low. In this example we change opacity every 3 frames instead of every frame, heavily reducing CPU overhead wihtout noticeable effect on smoothness. (CPU usage was reduced from 12% to 5% on a Quadcore 3GHz processor).
Because CSS3 animation relies on keyframes you would have to create a separate keyframe for each spinner hand, resulting in way too much calculations. The same spinner built with CSS3 animation resulted in 30% CPU usage.
Demo
The point of requestAnimationFrame is that it's efficiently called by the browser at as close to 60fps as possible, or whatever the frame-rate of the browser's animation engine is, and therefore you shouldn't be doing work in a requestAnimationFrame callback that would take longer than a frame-time. Keep in mind that in a browser, the execution of javascript happens very fast ... it takes a very complex amount of javascript calculations to actually take longer than a frame-time to execute. The main issues you'll run into are layout, painting, and redrawing elements on the screen. And for that, a web-worker isn't going to help you. A web-worker is only going to help you if you had really heavy javascript that would take longer than a frame-time to execute.
This is fairly easy to profile too ... You can look in Chrome's timeline tool to see how long your javascript function is taking to execute. Chances are it's on the order of only 1ms max, and if your animation is running at less than 60fps, it's because layout and repainting are taking longer than the remaining 16.7ms in the frame-time, but that's in the browser layout engine itself, and not something you can offload via a webworker anyways.
This code shows how long the browser takes just to send and receive a message to a worker. In my machine it takes about 3ms. You need to keep each frame of your JS under 10ms if you want to achieve 60fps (remember the browser still needs to style, layout, paint and composite each frame).
var myWorker,
send = document.querySelector('.send'),
receive = document.querySelector('.receive'),
time = document.querySelector('.time'),
start, end;
var sendMessage = function () {
start = performance.now();
myWorker.postMessage('My message');
console.log('Sending message to worker ' + start);
};
var receiveMessage = function(event) {
end = performance.now();
time.textContent = (end - start) + 'ms';
receive.textContent = event.data;
console.log('Message received from worker ' + end);
};
var workerFunction = function(event) {
self.postMessage('Worker response: ' + event.data);
};
var createWorker = function () {
if (window.Worker && window.Blob && window.URL) {
var workerContent = "self.onmessage = " + workerFunction.toString();
var blob = new Blob([workerContent], {type: 'application/javascript'});
myWorker = new Worker(URL.createObjectURL(blob));
myWorker.onmessage = receiveMessage;
}
};
createWorker();
send.addEventListener('click', sendMessage);
<button class="send">Send</button>
<p class="receive"></p>
<p class="time"></p>

JavaScript Noise Function Problems

I've been trying to learn about generating noise and find that I understand most of it but I'm having a bit of trouble with a script.
I used this page as a guide to write this script in JavaScript with the ultimate purpose of creating some noise on canvas.
It's definitely creating something but it's tucked all the way over on the left. Also, refreshing the page seems to create the same pattern over and over again.
What have I done wrong that the "noisy" part of the image is smushed on the left? How can I make it look more like the cloudy perlin noise?
I don't really understand why it doesn't produce a new pattern each time. What would I need to change in order to receive a random pattern each time the script is run?
Thank you for your help!
/* NOISE—Tie it all together
*/
function perlin2d(x,y){
var total = 0;
var p = persistence;
var n = octaves - 1;
for(var i = 0; i <= n; i++) {
var frequency = Math.pow(2, i);
var amplitude = Math.pow(p, i);
total = total + interpolatenoise(x * frequency, y * frequency) * amplitude;
}
return total;
}
I've forked your fiddle and fixed a couple things to make it work: http://jsfiddle.net/KkDVr/2/
The main problem was the flawed pseudorandom generator "noise", that always returned 1 for large enough values of x and y. I've replaced it with a random values table that is queried with integer coordinates:
var values = [];
for(var i = 0; i < height; i++) {
values[i] = [];
for(var j = 0; j < width; j++) {
values[i][j] = Math.random() * 2 - 1;
}
}
function noise(x, y) {
x = parseInt(Math.min(width - 1, Math.max(0, x)));
y = parseInt(Math.min(height - 1, Math.max(0, y)));
return values[x][y];
}
However, the implementation provided in the tutorial you followed uses simplified algorithms that are really poorly optimized. I suggest you the excellent real-world noise tutorial at http://scratchapixel.com/lessons/3d-advanced-lessons/noise-part-1.
Finally, maybe you could be interested in a project of mine: http://lencinhaus.github.com/canvas-noise.
It's a javascript app that renders perlin noise on an html5 canvas and allows to tweak almost any parameter visually. I've ported the original noise algorithm implementation by Ken Perlin to javascript, so that may be useful for you. You can find the source code here: https://github.com/lencinhaus/canvas-noise/tree/gh-pages.
Hope that helps, bye!

Seedable JavaScript random number generator

The JavaScript Math.random() function returns a random value between 0 and 1, automatically seeded based on the current time (similar to Java I believe). However, I don't think there's any way to set you own seed for it.
How can I make a random number generator that I can provide my own seed value for, so that I can have it produce a repeatable sequence of (pseudo)random numbers?
One option is http://davidbau.com/seedrandom which is a seedable RC4-based Math.random() drop-in replacement with nice properties.
If you don't need the seeding capability just use Math.random() and build helper functions around it (eg. randRange(start, end)).
I'm not sure what RNG you're using, but it's best to know and document it so you're aware of its characteristics and limitations.
Like Starkii said, Mersenne Twister is a good PRNG, but it isn't easy to implement. If you want to do it yourself try implementing a LCG - it's very easy, has decent randomness qualities (not as good as Mersenne Twister), and you can use some of the popular constants.
EDIT: consider the great options at this answer for short seedable RNG implementations, including an LCG option.
function RNG(seed) {
// LCG using GCC's constants
this.m = 0x80000000; // 2**31;
this.a = 1103515245;
this.c = 12345;
this.state = seed ? seed : Math.floor(Math.random() * (this.m - 1));
}
RNG.prototype.nextInt = function() {
this.state = (this.a * this.state + this.c) % this.m;
return this.state;
}
RNG.prototype.nextFloat = function() {
// returns in range [0,1]
return this.nextInt() / (this.m - 1);
}
RNG.prototype.nextRange = function(start, end) {
// returns in range [start, end): including start, excluding end
// can't modulu nextInt because of weak randomness in lower bits
var rangeSize = end - start;
var randomUnder1 = this.nextInt() / this.m;
return start + Math.floor(randomUnder1 * rangeSize);
}
RNG.prototype.choice = function(array) {
return array[this.nextRange(0, array.length)];
}
var rng = new RNG(20);
for (var i = 0; i < 10; i++)
console.log(rng.nextRange(10, 50));
var digits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
for (var i = 0; i < 10; i++)
console.log(rng.choice(digits));
If you want to be able to specify the seed, you just need to replace the calls to getSeconds() and getMinutes(). You could pass in an int and use half of it mod 60 for the seconds value and the other half modulo 60 to give you the other part.
That being said, this method looks like garbage. Doing proper random number generation is very hard. The obvious problem with this is that the random number seed is based on seconds and minutes. To guess the seed and recreate your stream of random numbers only requires trying 3600 different second and minute combinations. It also means that there are only 3600 different possible seeds. This is correctable, but I'd be suspicious of this RNG from the start.
If you want to use a better RNG, try the Mersenne Twister. It is a well tested and fairly robust RNG with a huge orbit and excellent performance.
EDIT: I really should be correct and refer to this as a Pseudo Random Number Generator or PRNG.
"Anyone who uses arithmetic methods to produce random numbers is in a state of sin."
--- John von Neumann
I use a JavaScript port of the Mersenne Twister:
https://gist.github.com/300494
It allows you to set the seed manually. Also, as mentioned in other answers, the Mersenne Twister is a really good PRNG.
The code you listed kind of looks like a Lehmer RNG. If this is the case, then 2147483647 is the largest 32-bit signed integer, 2147483647 is the largest 32-bit prime, and 48271 is a full-period multiplier that is used to generate the numbers.
If this is true, you could modify RandomNumberGenerator to take in an extra parameter seed, and then set this.seed to seed; but you'd have to be careful to make sure the seed would result in a good distribution of random numbers (Lehmer can be weird like that) -- but most seeds will be fine.
The following is a PRNG that may be fed a custom seed. Calling SeedRandom will return a random generator function. SeedRandom can be called with no arguments in order to seed the returned random function with the current time, or it can be called with either 1 or 2 non-negative inters as arguments in order to seed it with those integers. Due to float point accuracy seeding with only 1 value will only allow the generator to be initiated to one of 2^53 different states.
The returned random generator function takes 1 integer argument named limit, the limit must be in the range 1 to 4294965886, the function will return a number in the range 0 to limit-1.
function SeedRandom(state1,state2){
var mod1=4294967087
var mul1=65539
var mod2=4294965887
var mul2=65537
if(typeof state1!="number"){
state1=+new Date()
}
if(typeof state2!="number"){
state2=state1
}
state1=state1%(mod1-1)+1
state2=state2%(mod2-1)+1
function random(limit){
state1=(state1*mul1)%mod1
state2=(state2*mul2)%mod2
if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
return random(limit)
}
return (state1+state2)%limit
}
return random
}
Example use:
var generator1=SeedRandom() //Seed with current time
var randomVariable=generator1(7) //Generate one of the numbers [0,1,2,3,4,5,6]
var generator2=SeedRandom(42) //Seed with a specific seed
var fixedVariable=generator2(7) //First value of this generator will always be
//1 because of the specific seed.
This generator exhibit the following properties:
It has approximately 2^64 different possible inner states.
It has a period of approximately 2^63, plenty more than anyone will ever realistically need in a JavaScript program.
Due to the mod values being primes there is no simple pattern in the output, no matter the chosen limit. This is unlike some simpler PRNGs that exhibit some quite systematic patterns.
It discards some results in order to get a perfect distribution no matter the limit.
It is relatively slow, runs around 10 000 000 times per second on my machine.
Bonus: typescript version
If you program in Typescript, I adapted the Mersenne Twister implementation that was brought in Christoph Henkelmann's answer to this thread as a typescript class:
/**
* copied almost directly from Mersenne Twister implementation found in https://gist.github.com/banksean/300494
* all rights reserved to him.
*/
export class Random {
static N = 624;
static M = 397;
static MATRIX_A = 0x9908b0df;
/* constant vector a */
static UPPER_MASK = 0x80000000;
/* most significant w-r bits */
static LOWER_MASK = 0x7fffffff;
/* least significant r bits */
mt = new Array(Random.N);
/* the array for the state vector */
mti = Random.N + 1;
/* mti==N+1 means mt[N] is not initialized */
constructor(seed:number = null) {
if (seed == null) {
seed = new Date().getTime();
}
this.init_genrand(seed);
}
private init_genrand(s:number) {
this.mt[0] = s >>> 0;
for (this.mti = 1; this.mti < Random.N; this.mti++) {
var s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30);
this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
+ this.mti;
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
this.mt[this.mti] >>>= 0;
/* for >32 bit machines */
}
}
/**
* generates a random number on [0,0xffffffff]-interval
* #private
*/
private _nextInt32():number {
var y:number;
var mag01 = new Array(0x0, Random.MATRIX_A);
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (this.mti >= Random.N) { /* generate N words at one time */
var kk:number;
if (this.mti == Random.N + 1) /* if init_genrand() has not been called, */
this.init_genrand(5489);
/* a default initial seed is used */
for (kk = 0; kk < Random.N - Random.M; kk++) {
y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
this.mt[kk] = this.mt[kk + Random.M] ^ (y >>> 1) ^ mag01[y & 0x1];
}
for (; kk < Random.N - 1; kk++) {
y = (this.mt[kk] & Random.UPPER_MASK) | (this.mt[kk + 1] & Random.LOWER_MASK);
this.mt[kk] = this.mt[kk + (Random.M - Random.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
}
y = (this.mt[Random.N - 1] & Random.UPPER_MASK) | (this.mt[0] & Random.LOWER_MASK);
this.mt[Random.N - 1] = this.mt[Random.M - 1] ^ (y >>> 1) ^ mag01[y & 0x1];
this.mti = 0;
}
y = this.mt[this.mti++];
/* Tempering */
y ^= (y >>> 11);
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= (y >>> 18);
return y >>> 0;
}
/**
* generates an int32 pseudo random number
* #param range: an optional [from, to] range, if not specified the result will be in range [0,0xffffffff]
* #return {number}
*/
nextInt32(range:[number, number] = null):number {
var result = this._nextInt32();
if (range == null) {
return result;
}
return (result % (range[1] - range[0])) + range[0];
}
/**
* generates a random number on [0,0x7fffffff]-interval
*/
nextInt31():number {
return (this._nextInt32() >>> 1);
}
/**
* generates a random number on [0,1]-real-interval
*/
nextNumber():number {
return this._nextInt32() * (1.0 / 4294967295.0);
}
/**
* generates a random number on [0,1) with 53-bit resolution
*/
nextNumber53():number {
var a = this._nextInt32() >>> 5, b = this._nextInt32() >>> 6;
return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
}
}
you can than use it as follows:
var random = new Random(132);
random.nextInt32(); //return a pseudo random int32 number
random.nextInt32([10,20]); //return a pseudo random int in range [10,20]
random.nextNumber(); //return a a pseudo random number in range [0,1]
check the source for more methods.
Here is quite an effective but simple javascript PRNG function that I like to use:
// The seed is the base number that the function works off
// The modulo is the highest number that the function can return
function PRNG(seed, modulo) {
str = `${(2**31-1&Math.imul(48271,seed))/2**31}`
.split('')
.slice(-10)
.join('') % modulo
return str
}
I hope this is what you're looking for.
Thank you, #aaaaaaaaaaaa (Accepted Answer)
I really needed a good non-library solution (easier to embed)
so... i made this class to store the seed and allow a Unity-esque "Next" ... but kept the initial Integer based results
class randS {
constructor(seed=null) {
if(seed!=null) {
this.seed = seed;
} else {
this.seed = Date.now()%4645455524863;
}
this.next = this.SeedRandom(this.seed);
this.last = 0;
}
Init(seed=this.seed) {
if (seed = this.seed) {
this.next = this.SeedRandom(this.seed);
} else {
this.seed=seed;
this.next = this.SeedRandom(this.seed);
}
}
SeedRandom(state1,state2){
var mod1=4294967087;
var mod2=4294965887;
var mul1=65539;
var mul2=65537;
if(typeof state1!="number"){
state1=+new Date();
}
if(typeof state2!="number"){
state2=state1;
}
state1=state1%(mod1-1)+1;
state2=state2%(mod2-1)+1;
function random(limit){
state1=(state1*mul1)%mod1;
state2=(state2*mul2)%mod2;
if(state1<limit && state2<limit && state1<mod1%limit && state2<mod2%limit){
this.last = random;
return random(limit);
}
this.last = (state1+state2)%limit;
return (state1+state2)%limit;
}
this.last = random;
return random;
}
}
And then checked it with these... seems to work well with random (but queryable) seed value (a la Minecraft) and even stored the last value returned (if needed)
var rng = new randS(9005646549);
console.log(rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20)+' '+rng.next(20));
console.log(rng.next(20) + ' ' + rng.next(20) + ' ' + rng.last);
which should output (for everybody)
6 7 8 14 1 12 6
9 1 1
EDIT: I made the init() work if you ever needed to reseed, or were testing values (this was necessary in my context as well)
Note: This code was originally included in the question above. In the interests of keeping the question short and focused, I've moved it to this Community Wiki answer.
I found this code kicking around and it appears to work fine for getting a random number and then using the seed afterward but I'm not quite sure how the logic works (e.g. where the 2345678901, 48271 & 2147483647 numbers came from).
function nextRandomNumber(){
var hi = this.seed / this.Q;
var lo = this.seed % this.Q;
var test = this.A * lo - this.R * hi;
if(test > 0){
this.seed = test;
} else {
this.seed = test + this.M;
}
return (this.seed * this.oneOverM);
}
function RandomNumberGenerator(){
var d = new Date();
this.seed = 2345678901 + (d.getSeconds() * 0xFFFFFF) + (d.getMinutes() * 0xFFFF);
this.A = 48271;
this.M = 2147483647;
this.Q = this.M / this.A;
this.R = this.M % this.A;
this.oneOverM = 1.0 / this.M;
this.next = nextRandomNumber;
return this;
}
function createRandomNumber(Min, Max){
var rand = new RandomNumberGenerator();
return Math.round((Max-Min) * rand.next() + Min);
}
//Thus I can now do:
var letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var numbers = ['1','2','3','4','5','6','7','8','9','10'];
var colors = ['red','orange','yellow','green','blue','indigo','violet'];
var first = letters[createRandomNumber(0, letters.length)];
var second = numbers[createRandomNumber(0, numbers.length)];
var third = colors[createRandomNumber(0, colors.length)];
alert("Today's show was brought to you by the letter: " + first + ", the number " + second + ", and the color " + third + "!");
/*
If I could pass my own seed into the createRandomNumber(min, max, seed);
function then I could reproduce a random output later if desired.
*/
OK, here's the solution I settled on.
First you create a seed value using the "newseed()" function. Then you pass the seed value to the "srandom()" function. Lastly, the "srandom()" function returns a pseudo random value between 0 and 1.
The crucial bit is that the seed value is stored inside an array. If it were simply an integer or float, the value would get overwritten each time the function were called, since the values of integers, floats, strings and so forth are stored directly in the stack versus just the pointers as in the case of arrays and other objects. Thus, it's possible for the value of the seed to remain persistent.
Finally, it is possible to define the "srandom()" function such that it is a method of the "Math" object, but I'll leave that up to you to figure out. ;)
Good luck!
JavaScript:
// Global variables used for the seeded random functions, below.
var seedobja = 1103515245
var seedobjc = 12345
var seedobjm = 4294967295 //0x100000000
// Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
{
return [seednum]
}
// Works like Math.random(), except you provide your own seed as the first argument.
function srandom(seedobj)
{
seedobj[0] = (seedobj[0] * seedobja + seedobjc) % seedobjm
return seedobj[0] / (seedobjm - 1)
}
// Store some test values in variables.
var my_seed_value = newseed(230951)
var my_random_value_1 = srandom(my_seed_value)
var my_random_value_2 = srandom(my_seed_value)
var my_random_value_3 = srandom(my_seed_value)
// Print the values to console. Replace "WScript.Echo()" with "alert()" if inside a Web browser.
WScript.Echo(my_random_value_1)
WScript.Echo(my_random_value_2)
WScript.Echo(my_random_value_3)
Lua 4 (my personal target environment):
-- Global variables used for the seeded random functions, below.
seedobja = 1103515.245
seedobjc = 12345
seedobjm = 4294967.295 --0x100000000
-- Creates a new seed for seeded functions such as srandom().
function newseed(seednum)
return {seednum}
end
-- Works like random(), except you provide your own seed as the first argument.
function srandom(seedobj)
seedobj[1] = mod(seedobj[1] * seedobja + seedobjc, seedobjm)
return seedobj[1] / (seedobjm - 1)
end
-- Store some test values in variables.
my_seed_value = newseed(230951)
my_random_value_1 = srandom(my_seed_value)
my_random_value_2 = srandom(my_seed_value)
my_random_value_3 = srandom(my_seed_value)
-- Print the values to console.
print(my_random_value_1)
print(my_random_value_2)
print(my_random_value_3)

Categories

Resources