Generic organic stain/dirt shape - javascript

I am a searching for a algorithm which in essence returns an array of points which define the shape of a stain formed by liquid.
I need no directional influence, it could be a stain produced by a drop falling vertically down.
I started to spread points from a center in pairs of two, one closer to the center one near, which gives me a star ofcourse. But then i ideas left me. How can i avaoid intersections and the like but mostly: For a one-shape solution without any fancy sidedrops there should be a algorithm available i am not aware of? So any ideas/solutions?
Examples of stain i mean (i only need the central/main shape ofcourse):
related stains on Google-Images

You can refine you star approach by making the lines Bézier curves. If you look at the examples of your search, you will basically see two patterns of splashes: Small thin spikes and larger drop-like shapes.
We can ditsribute splashes randomly on a circle and also determine a splash length. Then we decide which of the shapes to draw based on that length. The control points we need are:
The code below tries to model that. The funtion splash returns a list of coordinates of a splash centered at the origin. The list has 3*n + 1 points for a closed curve of n Bézier segments. (n is determined randomly.)
The code is far from perfect and also has too much auxiliary stuff, which can be improved, but might give you an idea:
var rnd = {
uniform: function(n) {
return Math.floor(n * Math.random());
},
range: function(from, to) {
return from + this.uniform(to - from);
},
float: function(from, to) {
return from + (to - from) * Math.random();
}
}
var coord = {
radiants: function(x) {
return Math.PI * x / 180.0;
},
degrees: function(x) {
return 180.0 * x / Math.PI;
},
cartesian: function(P) {
return {
x: P.r * Math.cos(P.phi),
y: P.r * Math.sin(P.phi)
}
},
mid: function(P, Q, a) {
if (!a) a = 0.5;
return {
x: (1 - a) * P.x + a * Q.x,
y: (1 - a) * P.y + a * Q.y
};
},
normal: function(P, len) {
if (!len) len = 1;
var l = Math.sqrt(P.x*P.x + P.y*P.y);
return {
x: len * P.y / l,
y: -len * P.x / l
};
},
add: function(P, Q) {
return {
x: P.x + Q.x,
y: P.y + Q.y
};
},
mul: function(P, a) {
return {
x: a * P.x,
y: a * P.y
};
},
dist: function(P, Q) {
var dx = P.x - Q.x;
var dy = P.y - Q.y;
var l = Math.sqrt(dx*dx + dy*dy);
},
normalize: function(P, len) {
if (!len) len = 1;
var l = Math.sqrt(P.x*P.x + P.y*P.y);
return {
x: len * P.x / l,
y: len * P.y / l
};
}
}
function get(param, value, dflt) {
if (value in param) return param[value];
return dflt;
}
function splash(param) {
var r = get(param, "r", 10);
var minangle = get(param, "minangle", 5);
var maxangle = get(param, "maxangle", 30);
var ratio = get(param, "ratio", 2.4);
var n = get(param, "n", 2);
var radial = [];
var phi = 0;
while (phi < 2 * Math.PI) {
radial.push({
phi: phi,
r: r * (1 + (ratio - 1) * Math.pow(Math.random(), n))
});
phi += coord.radiants(rnd.float(minangle, maxangle, 30));
}
var phi0 = coord.radiants(rnd.float(0, 10));
for (var i = 0; i < radial.length; i++) {
var rr = radial[i];
rr.phi = 2 * rr.phi * Math.PI / phi + phi0;
}
var res = [];
var prev = radial[radial.length - 1];
var curr = radial[0];
var C = {x: 0, y: 0};
for (var i = 0; i < radial.length; i++) {
var next = radial[(i + 1) % radial.length];
var ML = coord.cartesian(prev);
var MR = coord.cartesian(next);
var M = coord.cartesian(curr);
var L = coord.mid(C, coord.mid(ML, M));
var R = coord.mid(C, coord.mid(MR, M));
if (i == 0) res.push(L);
var dphi = (next.phi - prev.phi);
if (dphi < 0) dphi += 2 * Math.PI;
var dr = 0.5 * r * dphi;
var NL = coord.normal(L, -dr * rnd.float(0.3, 0.45));
res.push(coord.add(L, NL));
console.log((curr.r - r) / (ratio - 1));
if (Math.random() > (curr.r - r) / r / (ratio - 1)) {
// little splash
var MM = coord.mid(C, M, rnd.float(0.75, 0.95));
res.push(MM);
res.push(M);
res.push(MM);
} else {
// drop-shaped splash
var s = dr * rnd.float(0.2, 0.5);
var t = dr * rnd.float(0.02, 0.2);
var MM = coord.mid(coord.mid(L, M), coord.mid(R, M));
var Mpos = coord.normalize(M, s);
var Mneg = coord.normalize(M, -s);
var MT = coord.add(M, Mpos);
var NML = coord.normal(M, s);
var NLL = coord.normal(M, t);
var MML = coord.add(MM, NLL);
var ML = coord.add(M, NML);
var NMR = coord.normal(M, -s);
var NRR = coord.normal(M, -t);
var MMR = coord.add(MM, NRR);
var MR = coord.add(M, NMR);
res.push(coord.mid(C, MML, 0.8));
res.push(MML);
res.push(coord.mid(C, MML, 1.25));
res.push(coord.add(ML, coord.mul(Mneg, 0.55)));
res.push(ML);
res.push(coord.add(ML, coord.mul(Mpos, 0.55)));
res.push(coord.add(MT, coord.mul(NML, 0.55)));
res.push(MT);
res.push(coord.add(MT, coord.mul(NMR, 0.55)));
res.push(coord.add(MR, coord.mul(Mpos, 0.55)));
res.push(MR);
res.push(coord.add(MR, coord.mul(Mneg, 0.55)));
res.push(coord.mid(C, MMR, 1.25));
res.push(MMR);
res.push(coord.mid(C, MMR, 0.8));
}
var NR = coord.normal(R, dr * rnd.float(0.3, 0.45));
res.push(coord.add(R, NR));
res.push(R);
prev = curr;
curr = next;
}
return res;
}
And an example of how to use that code:
window.onload = function() {
var cv = document.getElementById("plot");
var cx = cv.getContext("2d");
var p = splash({
r: 100,
ratio: 1.6,
n: 1
});
cx.fillStyle = "tomato";
cx.translate(300, 300);
cx.beginPath();
cx.moveTo(p[0].x, p[0].y);
for (var i = 1; i < p.length; i++) {
var p1 = p[i++];
var p2 = p[i++];
var p3 = p[i];
cx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
}
cx.closePath();
cx.fill();
}

Related

Issues with perlin noise having discontinuous edges

I created a simple perlin noise generator using p5.js, which is based on
this link
. For the most part, I got 80% of the algorithm working. The only issue is that there is defined discontinuities aligned with each gradient vector. the code for this project is shown below.
// noprotect
var screen_width = 400;
var screen_height = 400;
var res = 2;
var gvecs = {};
function setup() {
createCanvas(screen_width, screen_height);
initialize_gvecs();
draw_perlin_noise();
}
function draw() {
console.log(gvecs);
noLoop();
}
function initialize_gvecs() {
for (var y = 0; y <= res; y++) {
for (var x = 0; x <= res; x++) {
let theta = Math.random() * 2 * Math.PI;
gvecs[[x, y]] = {
x: Math.cos(theta),
y: Math.sin(theta)
};
}
}
}
function draw_perlin_noise() {
loadPixels();
for (var y = 0; y < screen_height; y++) {
for (var x = 0; x < screen_width; x++) {
var world_coordx = map(x, 0, screen_width, 0, res);
var world_coordy = map(y, 0, screen_height, 0, res);
var top_L_x = Math.floor(world_coordx);
var top_L_y = Math.floor(world_coordy);
var top_R_x = top_L_x + 1;
var top_R_y = top_L_y;
var bottom_L_x = top_L_x;
var bottom_L_y = top_L_y + 1;
var bottom_R_x = top_L_x + 1;
var bottom_R_y = top_L_y + 1;
var top_L_g = gvecs[[top_L_x, top_L_y]];
var top_R_g = gvecs[[top_R_x, top_R_y]];
var bottom_L_g = gvecs[[bottom_L_x, bottom_L_y]];
var bottom_R_g = gvecs[[bottom_R_x, bottom_R_y]];
var btw_top_L = {
x: world_coordx - top_L_x,
y: world_coordy - top_L_y
};
var btw_top_R = {
x: world_coordx - top_R_x,
y: world_coordy - top_R_y
};
var btw_bottom_L = {
x: world_coordx - bottom_L_x,
y: world_coordy - bottom_L_y
};
var btw_bottom_R = {
x: world_coordx - bottom_R_x,
y: world_coordy - bottom_R_y
};
var v = top_L_g.x * btw_top_L.x + top_L_g.y * btw_top_L.y;
var u = top_R_g.x * btw_top_R.x + top_R_g.y * btw_top_R.y;
var s = bottom_L_g.x * btw_bottom_L.x + bottom_L_g.y * btw_bottom_L.y;
var t = bottom_R_g.x * btw_bottom_R.x + bottom_R_g.y * btw_bottom_R.y;
var Sx = ease_curve(world_coordx - top_L_x);
var a = s + Sx * (t - s);
var b = u + Sx * (v - u);
var Sy = ease_curve(world_coordy - top_L_y);
var final_val = a + Sy * (b - a);
pixels[(x + y * screen_width) * 4] = map(final_val, -1, 1, 0, 255);
pixels[(x + y * screen_width) * 4 + 1] = map(final_val, -1, 1, 0, 255);
pixels[(x + y * screen_width) * 4 + 2] = map(final_val, -1, 1, 0, 255);
pixels[(x + y * screen_width) * 4 + 3] = 255;
}
}
updatePixels();
}
function ease_curve(x) {
return 6 * x ** 5 - 15 * x ** 4 + 10 * x ** 3;
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.5.0/lib/p5.js"></script>
an image of the issue I'm having is shown below.
I suspect that my issue has to do with the value between each gradient vector not properly using the adjacent gradient vectors, but I've tested and debugged this extensively and I cant find the issue. I've also tried several different ease_curve functions, but none of them seem to change anything. Any help would be greatly appreciated.
Bilinear interpolation might be twisted.
var Sx = ease_curve(world_coordx - top_L_x);
var a = s + Sx * (t - s);
//var b = u + Sx * (v - u);
var b = v + Sx * (u - v);
var Sy = ease_curve(world_coordy - top_L_y);
//var final_val = a + Sy * (b - a);
var final_val = b + Sy * (a - b);

Uncaught TypeError: (((item.value + 1) / total) * 100).floor is not a function

This code is running good here: https://jsfiddle.net/petersirka/fycx4kr1/
but not on other code editor like codepen or vscode getting error on line 33
//HTML
<svg id="svg" width="600" height="600"></svg>
//Javascript
function Donut(cx, cy, radius, data) {
function arcradius(cx, cy, radius, degrees) {`enter code here`
var radians = (degrees - 90) * Math.PI / 180.0;
return {
x: cx + (radius * Math.cos(radians)),
y: cy + (radius * Math.sin(radians))
};
}
var decimals = 4;
var total = 0;
var arr = [];
var beg = 0;
var end = 0;
var count = 0;
var half = radius / 2;
var midpoint = radius / 2.4;
for (var i = 0; i < data.length; i++)
total += data[i].value;
for (var i = 0; i < data.length; i++) {
var item = data[i];
var tmp = {};
var p = (((item.value + 1) / total) * 100).floor(2);
count += p;
if (i === length - 1 && count < 100)
p = p + (100 - count);
end = beg + ((360 / 100) * p);
tmp.index = i;
tmp.value = item.value;
tmp.data = item;
var b = arcradius(cx, cy, radius, end);
var e = arcradius(cx, cy, radius, beg);
var la = (end - beg) <= 180 ? 0 : 1;
tmp.d = ['M', b.x.floor(decimals), b.y.floor(decimals), 'A', radius, radius, 0, la, 0, e.x.floor(decimals), e.y.floor(decimals)].join(' ');
arr.push(tmp);
beg = end;
}
return arr;
}
// USAGE
(function(svg) {
var data = [{
value: 45
}, {
value: 25
}, {
value: 30
}];
var centerX = 300;
var centerY = 300;
var radius = 250;
var color = ['#7400b8', '#ff006e', '#3a86ff'];
var arr = Donut(centerX, centerY, radius, data);
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
svg.asvg('<g><path d="{0}" stroke="{1}" fill="none" stroke-width="50" /></g>'.format(item.d, color[i]));
}
})(document.getElementById('#svg'));
I know Math.Floor is a function because I've used it before and I looked it up. Did I mess something up in the code? Thanks.
Sorry if this question is a little vague, I'm still learning (as you can see :p).
If you want to convert the line
var p = (((item.value + 1) / total) * 100).floor(2);
into an integer with the floor function, you must write it like this:
var p = Math.floor(((item.value + 1) / total) * 100);
So the term you want to apply the Math.floor() function is inside the brackets.
I don't know what that (2) at the end means though...

Javascript canvas color not smooth change in brightness

Full demo of what I'm talking about is here:
https://ggodfrey.github.io/ComplexColor/index.html
I'm trying to make a website that can attempt to plot a complex-valued function using color. The hue is determined using the angle of the complex answer, and the brightness is determined by taking the log of the magnitude of the complex answer and then finding the fractional part. From there, I use a function to convert HSL to RGB, then putting that into an Image object that I draw onto the canvas, allowing me to draw on each pixel.
As seen on the page above, the brightness "levels" have "rough" edges between where the logarithm changes from one integer to another. It should look something more like this. Is this issue having to do with how I actually calculate the brightness or using the javascript canvas?
window.onload = function(){
var EQUATION = '';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var x_min = -3;
var x_max = 3;
var y_min = -3;
var y_max = 3;
var image = ctx.createImageData(canvas.width, canvas.height);
Complex = function(re, im){
this.re = re;
this.im = im;
}
Complex.prototype.add = function(other){
return new Complex(this.re+other.re, this.im+other.im);
}
Complex.prototype.multiply = function(other){
return new Complex(this.re*other.re-other.im*this.im, this.re*other.im+this.im*other.re);
}
Complex.prototype.power = function(num){
var r = this.magnitude();
var theta = this.angle();
var a = Math.pow(r, num)*Math.cos(num*theta);
var b = Math.pow(r, num)*Math.sin(num*theta);
return new Complex(a, b);
}
Complex.prototype.magnitude = function(){
return Math.pow(Math.pow(this.re, 2) + Math.pow(this.im, 2), 0.5);
}
Complex.prototype.angle = function(){
return Math.atan2(this.im, this.re);
}
Complex.prototype.divide = function(other){
x = new Complex(this.re, this.im);
y = new Complex(other.re, other.im);
x = x.multiply(new Complex(other.re, -other.im));
y = y.multiply(new Complex(other.re, -other.im));
x = new Complex(x.re/y.re, x.im/y.re);
return x;
}
function hslToRgb(h, s, l){ //NOT MY CODE
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3.0);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3.0);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function evaluate(i, j){
var z = new Complex(x_min+j*(x_max-x_min)/canvas.width, y_min+i*(y_max-y_min)/canvas.height);
var num = z.power(2).add(new Complex(-1, 0)).multiply(z.add(new Complex(-2, -1)).power(2));
var den = z.power(2).add(new Complex(2, 4));
var end = num.divide(den);
var color = end.angle()/(2*Math.PI);
var brightness = end.magnitude();
brightness = Math.log(brightness)/Math.log(2) % 1;
return [color, brightness];
}
function main(){
var data = image.data;
if(EQUATION !== null){
var count = 0;
for(var i=0;i<canvas.height;i++){
for(var j=0;j<canvas.width;j++){
var c = evaluate(i, j);
rgb = hslToRgb(c[0], 1, 0.4+c[1]/5);
var r = rgb[0];
var g = rgb[1];
var b = rgb[2];
var c = count*4;
data[c] = r;
data[c+1] = g;
data[c+2] = b;
data[c+3] = 255;
count++;
}
}
image.data = data;
ctx.putImageData(image, 0, 0);
}
}
main();
function getMousePos(canvas, evt){
var rect = canvas.getBoundingClientRect();
return {x: (evt.clientX-rect.left)/(rect.right-rect.left)*canvas.width,
y: (evt.clientY-rect.top)/(rect.bottom-rect.top)*canvas.height};
}
document.getElementById("submit").addEventListener("mousedown", function(event){
EQUATION = document.getElementById("equation").innerHTML;
var x = main();
})
document.getElementById("canvas").addEventListener("mousemove", function(event){
var loc = getMousePos(canvas, event);
document.getElementById('x').innerHTML = Math.round(loc.x*100)/100;
document.getElementById('y').innerHTML = Math.round(loc.y*100)/100;
document.getElementById('brightness').innerHTML = evaluate(loc.y, loc.x)[1];
})
}
<head>
<title>Complex Color</title>
<meta charset="utf-8"></head>
<body>
<input id="equation" type="text">Type Equation</input><button id="submit">Submit</button><br>
<canvas id="canvas" style="width:500px;height:500px"></canvas><p> <span id="x"></span>, <span id="y"></span>, <span id="brightness"></span></p>
</body>
Assuming the formulas are correct:
Increase the bitmap resolution of the canvas and use a smaller CSS size to introduce smoothing - or - implement a manual anti-aliasing. This is because you write on a pixel by pixel basis which bypasses anti-aliasing.
Decrease saturation to about 80%: rgb = hslToRgb(c[0], 0.8, 0.4 + c[1] / 5);. 100% will typically produce an over-saturated looking image on screen. For print though use 100%.
var EQUATION = '';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var x_min = -3;
var x_max = 3;
var y_min = -3;
var y_max = 3;
var image = ctx.createImageData(canvas.width, canvas.height);
Complex = function(re, im) {
this.re = re;
this.im = im;
}
Complex.prototype.add = function(other) {
return new Complex(this.re + other.re, this.im + other.im);
}
Complex.prototype.multiply = function(other) {
return new Complex(this.re * other.re - other.im * this.im, this.re * other.im + this.im * other.re);
}
Complex.prototype.power = function(num) {
var r = this.magnitude();
var theta = this.angle();
var a = Math.pow(r, num) * Math.cos(num * theta);
var b = Math.pow(r, num) * Math.sin(num * theta);
return new Complex(a, b);
}
Complex.prototype.magnitude = function() {
return Math.pow(Math.pow(this.re, 2) + Math.pow(this.im, 2), 0.5);
}
Complex.prototype.angle = function() {
return Math.atan2(this.im, this.re);
}
Complex.prototype.divide = function(other) {
x = new Complex(this.re, this.im);
y = new Complex(other.re, other.im);
x = x.multiply(new Complex(other.re, -other.im));
y = y.multiply(new Complex(other.re, -other.im));
x = new Complex(x.re / y.re, x.im / y.re);
return x;
}
function hslToRgb(h, s, l) { //NOT MY CODE
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3.0);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3.0);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function evaluate(i, j) {
var z = new Complex(x_min + j * (x_max - x_min) / canvas.width, y_min + i * (y_max - y_min) / canvas.height);
var num = z.power(2).add(new Complex(-1, 0)).multiply(z.add(new Complex(-2, -1)).power(2));
var den = z.power(2).add(new Complex(2, 4));
var end = num.divide(den);
var color = end.angle() / (2 * Math.PI);
var brightness = end.magnitude();
brightness = Math.log(brightness) / Math.log(2) % 1;
return [color, brightness];
}
function main() {
var data = image.data;
if (EQUATION !== null) {
var count = 0;
for (var i = 0; i < canvas.height; i++) {
for (var j = 0; j < canvas.width; j++) {
var c = evaluate(i, j);
rgb = hslToRgb(c[0], 0.8, 0.4 + c[1] / 5);
var r = rgb[0];
var g = rgb[1];
var b = rgb[2];
var c = count * 4;
data[c] = r;
data[c + 1] = g;
data[c + 2] = b;
data[c + 3] = 255;
count++;
}
}
image.data = data;
ctx.putImageData(image, 0, 0);
}
}
main();
#canvas {width:500px;height:500px}
<canvas id="canvas" width=1000 height=1000></canvas>
CSS' width and height are not the same as the attributes of a canvas element of the same name - They just stretch the canvas to the given size (also see this answer). Therefore canvas.width === 300 and canvas.height === 150, the default values. This low resolution creates your immediate problem. Here is your identical code and just the canvas' attributes set properly instead of using incorrect css:
window.onload = function(){
var EQUATION = '';
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var x_min = -3;
var x_max = 3;
var y_min = -3;
var y_max = 3;
var image = ctx.createImageData(canvas.width, canvas.height);
Complex = function(re, im){
this.re = re;
this.im = im;
}
Complex.prototype.add = function(other){
return new Complex(this.re+other.re, this.im+other.im);
}
Complex.prototype.multiply = function(other){
return new Complex(this.re*other.re-other.im*this.im, this.re*other.im+this.im*other.re);
}
Complex.prototype.power = function(num){
var r = this.magnitude();
var theta = this.angle();
var a = Math.pow(r, num)*Math.cos(num*theta);
var b = Math.pow(r, num)*Math.sin(num*theta);
return new Complex(a, b);
}
Complex.prototype.magnitude = function(){
return Math.pow(Math.pow(this.re, 2) + Math.pow(this.im, 2), 0.5);
}
Complex.prototype.angle = function(){
return Math.atan2(this.im, this.re);
}
Complex.prototype.divide = function(other){
x = new Complex(this.re, this.im);
y = new Complex(other.re, other.im);
x = x.multiply(new Complex(other.re, -other.im));
y = y.multiply(new Complex(other.re, -other.im));
x = new Complex(x.re/y.re, x.im/y.re);
return x;
}
function hslToRgb(h, s, l){ //NOT MY CODE
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
} else {
function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3.0);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3.0);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function evaluate(i, j){
var z = new Complex(x_min+j*(x_max-x_min)/canvas.width, y_min+i*(y_max-y_min)/canvas.height);
var num = z.power(2).add(new Complex(-1, 0)).multiply(z.add(new Complex(-2, -1)).power(2));
var den = z.power(2).add(new Complex(2, 4));
var end = num.divide(den);
var color = end.angle()/(2*Math.PI);
var brightness = end.magnitude();
brightness = Math.log(brightness)/Math.log(2) % 1;
return [color, brightness];
}
function main(){
var data = image.data;
if(EQUATION !== null){
var count = 0;
for(var i=0;i<canvas.height;i++){
for(var j=0;j<canvas.width;j++){
var c = evaluate(i, j);
rgb = hslToRgb(c[0], 1, 0.4+c[1]/5);
var r = rgb[0];
var g = rgb[1];
var b = rgb[2];
var c = count*4;
data[c] = r;
data[c+1] = g;
data[c+2] = b;
data[c+3] = 255;
count++;
}
}
image.data = data;
ctx.putImageData(image, 0, 0);
}
}
main();
function getMousePos(canvas, evt){
var rect = canvas.getBoundingClientRect();
return {x: (evt.clientX-rect.left)/(rect.right-rect.left)*canvas.width,
y: (evt.clientY-rect.top)/(rect.bottom-rect.top)*canvas.height};
}
document.getElementById("submit").addEventListener("mousedown", function(event){
EQUATION = document.getElementById("equation").innerHTML;
var x = main();
})
document.getElementById("canvas").addEventListener("mousemove", function(event){
var loc = getMousePos(canvas, event);
document.getElementById('x').innerHTML = Math.round(loc.x*100)/100;
document.getElementById('y').innerHTML = Math.round(loc.y*100)/100;
document.getElementById('brightness').innerHTML = evaluate(loc.y, loc.x)[1];
})
}
<input id="equation" type="text">Type Equation</input>
<canvas id="canvas" width="500" height="500"></canvas>
<button id="submit">Submit</button><br><span id="x"></span>, <span id="y"></span>, <span id="brightness"></span></p>
Afterwards, increasing the resolution and "stretching smaller" (a kind of supersampling) as described in another answer helps further, but is not the core issue.

To find coordinates of nearest point on a line segment from a point

I need to calculate the foot of a perpendicular line drawn from a point P to a line segment AB. I need coordinates of point C where PC is perpendicular drawn from point P to line AB.
I found few answers on SO here but the vector product process does not work for me.
Here is what I tried:
function nearestPointSegment(a, b, c) {
var t = nearestPointGreatCircle(a,b,c);
return t;
}
function nearestPointGreatCircle(a, b, c) {
var a_cartesian = normalize(Cesium.Cartesian3.fromDegrees(a.x,a.y))
var b_cartesian = normalize(Cesium.Cartesian3.fromDegrees(b.x,b.y))
var c_cartesian = normalize(Cesium.Cartesian3.fromDegrees(c.x,c.y))
var G = vectorProduct(a_cartesian, b_cartesian);
var F = vectorProduct(c_cartesian, G);
var t = vectorProduct(G, F);
t = multiplyByScalar(normalize(t), R);
return fromCartesianToDegrees(t);
}
function vectorProduct(a, b) {
var result = new Object();
result.x = a.y * b.z - a.z * b.y;
result.y = a.z * b.x - a.x * b.z;
result.z = a.x * b.y - a.y * b.x;
return result;
}
function normalize(t) {
var length = Math.sqrt((t.x * t.x) + (t.y * t.y) + (t.z * t.z));
var result = new Object();
result.x = t.x/length;
result.y = t.y/length;
result.z = t.z/length;
return result;
}
function multiplyByScalar(normalize, k) {
var result = new Object();
result.x = normalize.x * k;
result.y = normalize.y * k;
result.z = normalize.z * k;
return result;
}
function fromCartesianToDegrees(pos) {
var carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(pos);
var lon = Cesium.Math.toDegrees(carto.longitude);
var lat = Cesium.Math.toDegrees(carto.latitude);
return [lon,lat];
}
What I am missing in this?
Here's a vector-based way:
function foot(A, B, P) {
const AB = {
x: B.x - A.x,
y: B.y - A.y
};
const k = ((P.x - A.x) * AB.x + (P.y - A.y) * AB.y) / (AB.x * AB.x + AB.y * AB.y);
return {
x: A.x + k * AB.x,
y: A.y + k * AB.y
};
}
const A = { x: 1, y: 1 };
const B = { x: 4, y: 5 };
const P = { x: 4.5, y: 3 };
const C = foot(A, B, P);
console.log(C);
// perpendicular?
const AB = {
x: B.x - A.x,
y: B.y - A.y
};
const PC = {
x: C.x - P.x,
y: C.y - P.y
};
console.log((AB.x * PC.x + AB.y * PC.y).toFixed(3));
Theory:
I start with the vector from A to B, A➞B. By multiplying this vector by a scalar k and adding it to point A I can get to any point C on the line AB.
I) C = A + k × A➞B
Next I need to establish the 90° angle, which means the dot product of A➞B and P➞C is zero.
II) A➞B · P➞C = 0
Now solve for k.
function closestPointOnLineSegment(pt, segA, segB) {
const A = pt.x - segA.x,
B = pt.y - segA.y,
C = segB.x - segA.x,
D = segB.y - segA.y
const segLenSq = C**2 + D**2
const t = (segLenSq != 0) ? (A*C + B*D) / segLenSq : -1
return (t<0) ? segA : (t>1) ? segB : {
x: segA.x + t * C,
y: segA.y + t * D
}
}
can.width = can.offsetWidth
can.height = can.offsetHeight
const ctx = can.getContext('2d')
const segA = {x:100,y:100},
segB = {x:400, y:200},
pt = {x:250, y:250}
visualize()
function visualize() {
ctx.clearRect(0, 0, can.width, can.height)
const t = Date.now()
pt.x = Math.cos(t/1000) * 150 + 250
pt.y = Math.sin(t/1000) * 100 + 150
segA.x = Math.cos(t / 2000) * 50 + 150
segA.y = Math.sin(t / 2500) * 50 + 50
segB.x = Math.cos(t / 3000) * 75 + 400
segB.y = Math.sin(t / 2700) * 75 + 100
line(segA, segB, 'gray', 2)
const closest = closestPointOnLineSegment(pt, segA, segB)
ctx.setLineDash([5, 8])
line(pt, closest, 'orange', 2)
ctx.setLineDash([])
dot(closest, 'rgba(255, 0, 0, 0.8)', 10)
dot(pt, 'blue', 7)
dot(segA, 'black', 7)
dot(segB, 'black', 7)
window.requestAnimationFrame(visualize)
}
function dot(p, color, w) {
ctx.fillStyle = color
ctx.fillRect(p.x - w/2, p.y - w/2, w, w)
}
function line(a, b, color, n) {
ctx.strokeStyle = color
ctx.lineWidth = n
ctx.beginPath()
ctx.moveTo(a.x, a.y)
ctx.lineTo(b.x, b.y)
ctx.stroke()
}
html, body { height:100%; min-height:100%; margin:0; padding:0; overflow:hidden }
canvas { width:100%; height:100%; background:#ddd }
<canvas id="can"></canvas>

I cannot generate smooth Simplex noise in Javascript

I've tried everything and read every single link I can see on the internet regarding Perlin Noise or Simplex Noise and even dissected a few Javascript examples that I see work fine.
But I still get very random looking images... essentially just TV static.
My code is below. I'm using a random number generator so that I can seed a value, but I've tried with Math.random as well.
As near as I can tell, the different images generated at the different octaves aren't interpolating properly, or maybe the way I'm converting from the Noise function to RGB values is wrong (I've tried to fix both of these issues...).
if (!this.Prng) {
var Prng = function() {
var iMersenne = 2147483647;
var rnd = function(seed) {
if (arguments.length) {
that.seed = arguments[0];
}
that.seed = that.seed*16807%iMersenne;
return that.seed;
};
var that = {
seed: 123,
rnd: rnd,
random: function(seed) {
if (arguments.length) {
that.seed = arguments[0];
}
return rnd()/iMersenne;
}
};
return that;
}();
}
var CSimplexNoise = function(r)
{
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]];
var p = [];
for(i = 0; i < 256; i++)
p[i] = Math.floor(r.random()*256);
this.perm = new Array();
for(i = 0; i < 512; i++)
{
this.perm[i] = p[i & 255];
}
}
CSimplexNoise.prototype.dot = function(g,x,y)
{
return g[0]*x + g[1]*y;
}
CSimplexNoise.prototype.GenerateSimplexNoise = function(x,y,octaves,persistence)
{
var total = 0;
for(i=0; i < octaves-1; i++)
{
var freq = Math.pow(2,i);
var amp = Math.pow(persistence,i);
total += this.InterpolatedNoise(x*freq,y*freq) * amp;
}
return total;
}
CSimplexNoise.prototype.InterpolatedNoise = function(x,y)
{
var xInt = Math.floor(x);
var xFrac = x - xInt;
var yInt = Math.floor(y);
var yFrac = y - yInt;
var v1 = this.SmoothNoise(xInt,yInt);
var v2 = this.SmoothNoise(xInt + 1,yInt)
var v3 = this.SmoothNoise(xInt,yInt+1)
var v4 = this.SmoothNoise(xInt + 1, yInt + 1);
var i1 = this.LinearInterpolate(v1,v2,xFrac);
var i2 = this.LinearInterpolate(v3,v4,xFrac);
return this.CosineInterpolate(i1,i2,yFrac);
}
CSimplexNoise.prototype.LinearInterpolate = function(a,b,x)
{
return a*(1-x) + b*x;
}
CSimplexNoise.prototype.CosineInterpolate = function(a,b,x)
{
var f = (1 - Math.cos(x*Math.PI)) * 0.5;
return a*(1-f) + b*f;
}
CSimplexNoise.prototype.SmoothNoise = function(x,y)
{
var corners = (this.Noise(x-1,y-1) + this.Noise(x+1,y-1) + this.Noise(x-1,y+1) + this.Noise(x+1,y+1)) / 16;
var sides = (this.Noise(x-1,y) + this.Noise(x+1,y) + this.Noise(x,y-1) + this.Noise(x+1,y+1)) / 8;
var center = this.Noise(x,y) / 4;
return corners + sides + center;
}
CSimplexNoise.prototype.Noise = function(xin, yin)
{
var n0, n1, n2;
var F2 = 0.5*(Math.sqrt(3)-1);
var s = (xin+yin)*F2;
var i = Math.floor(xin+s);
var j = Math.floor(yin+s);
var G2 = (3-Math.sqrt(3))/6;
var t = (i+j)*G2;
var X0 = i-t;
var Y0 = j-t;
var x0 = xin-X0;
var y0 = yin-Y0;
var i1,j1;
if(x0 > y0)
{
i1 = 1;
j1 = 0;
}
else
{
i1 = 0;
j1 = 1;
}
var x1 = x0 - i1 + G2;
var y1 = y0 - j1 + G2;
var x2 = x0 - 1 + 2 * G2;
var y2 = y0 - 1 + 2 * G2;
var ii = i & 255;
var jj = j & 255;
var gi0 = this.perm[ii + this.perm[jj]] % 12;
var gi1 = this.perm[ii + i1 + this.perm[jj + j1]] % 12;
var gi2 = this.perm[ii + 1 + this.perm[jj + 1]] % 12;
var t0 = 0.5 - x0 * x0 - y0 * y0;
if(t0 < 0)
n0 = 0;
else
{
t0 *= t0;
n0 = t0 * t0 * this.dot(this.grad3[gi0],x0,y0)
}
var t1 = 0.5 - x1 * x1 - y1 * y1;
if(t1 < 0)
n1 = 0;
else
{
t1 *= t1;
n1 = t1 * t1 * this.dot(this.grad3[gi1],x1,y1);
}
var t2 = 0.5 - x2 * x2 - y2 * y2;
if(t2 <0 )
n2 = 0;
else
{
t2 *= t2;
n2 = t2 * t2 * this.dot(this.grad3[gi2],x2,y2);
}
return 70 * (n0 + n1 + n2);
}
$(document).ready(function(){
var context = $('#screen')[0].getContext("2d");
var w = 100;
var h = 100;
var data = context.createImageData(w,h);
var simplexNoise = new CSimplexNoise(Prng);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
// var newVal = ((simplexNoise.GenerateSimplexNoise(x,y,5,0.25) - -1) / (1 - -1)) * (255 - 0);
var newVal2 = simplexNoise.GenerateSimplexNoise(x,y,5,0.5)
var newVal = Math.floor(newVal2*256);
newVal = Math.abs(newVal * 2)-0.5;
data.data[((h * y) + x) * 4] = newVal;
data.data[((h * y) + x) * 4+1] = newVal;
data.data[((h * y) + x) * 4+2] = newVal;
data.data[((h * y) + x) * 4+3] = 255;
}
}
context.putImageData(data,0,0);
})
Try sampling simplexNoise.GenerateSimplexNoise(x * 0.05, y * 0.05, 5, 0.5)
The problem may be that your samples are too far apart. (this would result in apparently random behavior, since the simplex noise might go through more than half a wavelength before you sample it)
REVISION: Updated numbers above...
You may actually need to reduce the samples so that there are 20 in a given wavelength of the simplex noise. The average wavelength of most simplex noise is 1, so 0.05 should do the trick. Also, you may want to test with just one octave at first.

Categories

Resources