Fourier series simulator - javascript

I've written a drawing app that turns drawings into Fourier series, but it's doing a weird thing where it spirals inwards. For some reason, all the constants of the series are similar in size (something that I believe is incorrect, but I could be wrong). Here's my code:
var states = ["START", "DRAWING", "CIRCLES"];
var currentState = states[0];
var graph = [];
var constants = [];
// Half because ranging from -50 to 50
var halfNumCircles = 50;
var time = 0;
var deltaTime = 0.01;
// INITIAL SETUP
function setup() {
createCanvas(window.innerWidth, window.innerHeight);
angleMode(DEGREES);
frameRate(30);
cursor(CROSS);
}
// DRAWING LOOP
function draw() {
background(255);
// Axes
stroke(100);
line(width / 2, 0, width / 2, height);
line(0, height / 2, width, height / 2);
// Drawing
stroke(0);
if (currentState == states[1]) {
// Add mousepos to graph
graph.push([mouseX - width / 2, mouseY - height / 2]);
// Draw graph
for (let i = 0; i < graph.length - 1; i++) {
line(graph[i][0] + width / 2, graph[i][1] + height / 2,
graph[i + 1][0] + width / 2, graph[i + 1][1] + height / 2);
}
}
// Circles
stroke(0);
if (currentState == states[2]) {
// Starting at origin, draw lines to each boundary between circles
var points = [[0, 0]];
// For each constant, add a point
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
// n is 0,1,-1,2,-2...
var n = 0;
if (i % 2 == 0) {
n = -i / 2;
} else {
n = i / 2;
}
var pointX = constants[i][0] * cos(n * 2 * Math.PI * time) -
constants[i][1] * sin(n * 2 * Math.PI * time);
var pointY = constants[i][0] * sin(n * 2 * Math.PI * time) +
constants[i][1] * cos(n * 2 * Math.PI * time);
// Add new arrow to the last one
points.push([points[points.length - 1][0] + pointX, points[points.length - 1][1] + pointY]);
}
// Draw lines between points
for (let i = 0; i < points.length - 1; i++) {
line(points[i][0] + width / 2, points[i][1] + height / 2,
points[i + 1][0] + width / 2, points[i + 1][1] + height / 2)
}
// Increment time
time = (time + deltaTime);
}
}
// FOURIER SERIES OF FUNCTION
function getConstants(graph) {
// Returns array with constants
// Note that constants are complex numbers
// Set constants to 0, to be added to in the next loop
var constants = []
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
constants.push([0, 0]);
}
// For each constant
for (let c = -halfNumCircles; c <= halfNumCircles; c++) {
var deltaT = 1.0 / graph.length;
// Loop through the graph: sum of f(t)*e^{-c*2pi*i*t}*deltaT from 0 <= t <= 1
for (let i = 0; i < graph.length; i++) {
// Effective points on graph
var a = graph[i][0];
var b = graph[i][1];
var t = i / graph.length;
// Complex multiplication f(t)*e^{-c*2pi*i*t}
var xChange = a * cos(-c * 2 * Math.PI * t) - b * sin(-c * 2 * Math.PI * t);
var yChange = a * sin(-c * 2 * Math.PI * t) + b * cos(-c * 2 * Math.PI * t);
constants[c + halfNumCircles][0] += xChange * deltaT;
constants[c + halfNumCircles][1] += yChange * deltaT;
}
}
// Reorder from [...-2, -1, 0, 1, 2...] to [0, 1, -1, 2, -2...]
var orderedConstants = []
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
orderedConstants.push([0, 0]);
}
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
if (i % 2 == 0) {
orderedConstants[i] = constants[halfNumCircles - i / 2];
} else {
orderedConstants[i] = constants[halfNumCircles + (i + 1) / 2];
}
}
return orderedConstants;
}
// STATE CHANGING EVENTS
function mousePressed() {
// When clicked from start, start drawing
// When clicked from circles, reset
if (currentState == states[0]) {
currentState = states[1];
} else if (currentState == states[2]) {
currentState = states[0];
graph = [[]];
}
}
function mouseReleased() {
// When released, stop drawing, start circles
if (currentState == states[1]) {
currentState = states[2];
time = 0;
// Add first element of graph to the end, creating a loop
graph.push(graph[0]);
// Computationally intensive step
constants = getConstants(graph);
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Circle Drawing</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.0/p5.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.0/addons/p5.dom.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.0/addons/p5.sound.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/5.2.3/math.js" type="text/javascript"></script>
<script src="circles.js" type="text/javascript"></script>
<style media="screen">
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
</body>
</html>
I can't seem to find the bug. I figured it could have something to do with the scale of the input, but that wouldn't' make sense since the units are arbitrary in this case. Please let me know if you have any idea what I've done wrong. Thanks for your help!

The unit of deltaTime is milliseconds. The sample period is far too large.
Divide the time by 1000.0 to get the time in seconds:
var times_s = time/1000.0;
var pointX = constants[i][0] * cos(n * 2 * Math.PI * times_s) -
constants[i][1] * sin(n * 2 * Math.PI * times_s);
var pointY = constants[i][0] * sin(n * 2 * Math.PI * times_s) +
constants[i][1] * cos(n * 2 * Math.PI * times_s);
var states = ["START", "DRAWING", "CIRCLES"];
var currentState = states[0];
var graph = [];
var constants = [];
// Half because ranging from -50 to 50
var halfNumCircles = 50;
var time = 0;
var deltaTime = 0.01;
// INITIAL SETUP
function setup() {
createCanvas(window.innerWidth, window.innerHeight);
angleMode(DEGREES);
frameRate(30);
cursor(CROSS);
}
// DRAWING LOOP
function draw() {
background(255);
// Axes
stroke(100);
line(width / 2, 0, width / 2, height);
line(0, height / 2, width, height / 2);
// Drawing
stroke(0);
if (currentState == states[1]) {
// Add mousepos to graph
graph.push([mouseX - width / 2, mouseY - height / 2]);
// Draw graph
for (let i = 0; i < graph.length - 1; i++) {
line(graph[i][0] + width / 2, graph[i][1] + height / 2,
graph[i + 1][0] + width / 2, graph[i + 1][1] + height / 2);
}
}
// Circles
stroke(0);
if (currentState == states[2]) {
// Starting at origin, draw lines to each boundary between circles
var points = [[0, 0]];
// For each constant, add a point
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
// n is 0,1,-1,2,-2...
var n = 0;
if (i % 2 == 0) {
n = -i / 2;
} else {
n = i / 2;
}
var times_s = time/1000.0;
var pointX = constants[i][0] * cos(n * 2 * Math.PI * times_s) -
constants[i][1] * sin(n * 2 * Math.PI * times_s);
var pointY = constants[i][0] * sin(n * 2 * Math.PI * times_s) +
constants[i][1] * cos(n * 2 * Math.PI * times_s);
// Add new arrow to the last one
points.push([points[points.length - 1][0] + pointX, points[points.length - 1][1] + pointY]);
}
// Draw lines between points
for (let i = 0; i < points.length - 1; i++) {
line(points[i][0] + width / 2, points[i][1] + height / 2,
points[i + 1][0] + width / 2, points[i + 1][1] + height / 2)
}
// Increment time
time = (time + deltaTime);
}
}
// FOURIER SERIES OF FUNCTION
function getConstants(graph) {
// Returns array with constants
// Note that constants are complex numbers
// Set constants to 0, to be added to in the next loop
var constants = []
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
constants.push([0, 0]);
}
// For each constant
for (let c = -halfNumCircles; c <= halfNumCircles; c++) {
var deltaT = 1.0 / graph.length;
// Loop through the graph: sum of f(t)*e^{-c*2pi*i*t}*deltaT from 0 <= t <= 1
for (let i = 0; i < graph.length; i++) {
// Effective points on graph
var a = graph[i][0];
var b = graph[i][1];
var t = i / graph.length;
// Complex multiplication f(t)*e^{-c*2pi*i*t}
var xChange = a * cos(-c * 2 * Math.PI * t) - b * sin(-c * 2 * Math.PI * t);
var yChange = a * sin(-c * 2 * Math.PI * t) + b * cos(-c * 2 * Math.PI * t);
constants[c + halfNumCircles][0] += xChange * deltaT;
constants[c + halfNumCircles][1] += yChange * deltaT;
}
}
// Reorder from [...-2, -1, 0, 1, 2...] to [0, 1, -1, 2, -2...]
var orderedConstants = []
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
orderedConstants.push([0, 0]);
}
for (let i = 0; i < 2 * halfNumCircles + 1; i++) {
if (i % 2 == 0) {
orderedConstants[i] = constants[halfNumCircles - i / 2];
} else {
orderedConstants[i] = constants[halfNumCircles + (i + 1) / 2];
}
}
return orderedConstants;
}
// STATE CHANGING EVENTS
function mousePressed() {
// When clicked from start, start drawing
// When clicked from circles, reset
if (currentState == states[0]) {
currentState = states[1];
} else if (currentState == states[2]) {
currentState = states[0];
graph = [[]];
}
}
function mouseReleased() {
// When released, stop drawing, start circles
if (currentState == states[1]) {
currentState = states[2];
time = 0;
// Add first element of graph to the end, creating a loop
graph.push(graph[0]);
// Computationally intensive step
constants = getConstants(graph);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.js"></script>

Related

p5.js replace let url to local link

Im noob with p5j and i need help from someone please! :-(
On the website i must upload it , they not allow the external links.
and i need to change the "let url" to a png/jpg local link ( not external link ).
i dont know what the solution is ( maybe something similar with: "loadImage" or something.... please help me :-) )
Thank you so much and have a blessed week!
Here is the code:
//let url = "https://coolors.co/3a2e39-1e555c-f4d8cd-edb183-f15152";
let url = "https://blog.logrocket.com/wp-content/uploads/2022/03/Creating-animations-p5-js.png";
let palette;
let font;
function preload() {
font = loadFont("https://openprocessing.org/sketch/1359269/files/Happy Monsters.ttf");
}
function setup() {
createCanvas(1112, 834);
colorMode(HSB, 360, 100, 100, 100);
angleMode(DEGREES);
palette = createPalette(url);
background(10);
}
function draw() {
//background(0, 0, 90);
let offset = 100//width / 100;
let margin = 0; //offset / 5;
let cells = 1//int(random(2, 8));
let d = (width - offset * 2 - margin * (cells - 1)) / cells;
for (let j = 0; j < cells; j++) {
for (let i = 0; i < cells; i++) {
let x = offset + i * (d + margin) + d / 2;
let y = offset + j * (d + margin) + d / 2;
drawFancyShape(x, y, d, palette.concat());
}
}
frameRate(0.5);
noLoop();
}
function drawFancyShape(x, y, d, colors, char = String.fromCodePoint(65 + int(random(26)))) {
let g = createGraphics(d, d);
let g2 = createGraphics(d, d);
colors = shuffle(colors);
let c0 = colors[0];
colors.splice(0, 1);
let ratio = 0.2;
let xStep, yStep;
for (let y = 0; y < g.height; y += yStep) {
yStep = random(ratio, 1 - ratio) * g.height / 2;
if (y + yStep > g.height) yStep = g.height - y;
if (g.height - y - yStep < g.height / 100) yStep = g.height - y;
for (let x = 0; x < g.width; x += xStep) {
xStep = random(ratio, 1 - ratio) * g.width / 2;
if (x + xStep > g.width) xStep = g.width - x;
if (g.width - x - xStep < g.width / 100) xStep = g.width - x;
let r = [];
for (let i = 0; i < 4; i++) {
r.push(int(random(5)) * max(xStep, yStep) / 4);
}
g.rectMode(CENTER);
g.fill(random(colors));
g.noStroke();
g.rect(x + xStep / 2, y + yStep / 2, xStep - 2, yStep - 2, r[0], r[1], r[2], r[3]);
}
}
g2.textSize(g.width * 0.6);
g2.textAlign(CENTER, CENTER);
g2.textFont(font)
g2.textStyle(BOLD);
g2.fill(c0);
// g2.stroke(0);
g2.text(char, g.width / 2, g.height / 2 - g.height / 8);
let g_tmp = g.get();
let g2_tmp = g2.get();
g_tmp.mask(g2_tmp);
// g_tmp.mask(g2_tmp);
drawingContext.shadowColor = color(0, 0, 0, 33);
drawingContext.shadowBlur = d / 10;
push();
translate(x, y);
imageMode(CENTER);
// image(g, 0, 0);
let scl = 1.1;
image(g2, 0, 0, g2.width * scl, g2.height * scl);
image(g_tmp, 0, 0, g_tmp.width * scl, g_tmp.height * scl);
pop();
}
function createPalette(_url) {
let slash_index = _url.lastIndexOf('/');
let pallate_str = _url.slice(slash_index + 1);
let arr = pallate_str.split('-');
for (let i = 0; i < arr.length; i++) {
arr[i] = color('#' + arr[i]);
}
return arr;
}
// save jpg
let lapse = 0; // mouse timer
function mousePressed(){
// prevents mouse press from registering twice
if (millis() - lapse > 400){
save("img_" + month() + '-' + day() + '_' + hour() + '-' + minute() + '-' + second() + ".jpg");
lapse = millis();
}
}

rotating a custom shape moves it in the corner

When rotating a custom shape using translate(width/2,height/2); rotate(angle)),
it moves the shape in the bottom left corner. I tried making translate values negative, it fixed it but then the origin was at 0,0. I have used pop(); push() and beginShape(); endShape with no success.
var points = [];
var r;
var lines = 30;
function setup() {
createCanvas(window.innerWidth, window.windowHeight);
angleMode(DEGREES);
// get the points of the corners of the hexagon
r = Math.min(width, height) * 0.4;
var angle = 60;
for (var i = 1; i < 7; i++) {
var tempX = r * sin((angle * i + 30) % 360) + width / 2;
var tempY = r * cos((angle * i + 30) % 360) + height / 2;
points.push([tempX, tempY]);
}
background(0);
stroke(0, 0, 255);
rectMode(CENTER);
}
function draw() {
background(0);
// draw the lines of ...
push();
translate(width/2, height/2);
rotate(frameCount * 0.75);
beginShape();
for (var i = 0; i < points.length; i++) {
// ... the hexagon perimeter
line(points[i][0], points[i][1], points[(i + 1) % 6][0], points[(i + 1) % 6][1]);
var tempAngle = 240 + i * 60;
var tempX = r * 1.1545 * sin(tempAngle) + points[i][0];
var tempY = r * 1.1545 * cos(tempAngle) + points[i][1];
for (var j = 0; j < lines + 1; j++) {
// ... the lines inside the hexagon
var tempAngle2 = tempAngle = (30 / lines * j) + 210 + i * 60;
var distance = r / cos(30 / lines * j);
var tempX2 = distance * sin(tempAngle2) + points[i][0];
var tempY2 = distance * cos(tempAngle2) + points[i][1];;
line(points[i][0], points[i][1], tempX2, tempY2);
}
endShape();
}
pop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.js"></script>
I think the problem is that you are defining your points for your shape with an x/y offset. By removing the width / 2 & height / 2 from your point definitions it centers your shape.
var points = [];
var r;
var lines = 30;
function setup() {
createCanvas(window.innerWidth, window.windowHeight);
angleMode(DEGREES);
// get the points of the corners of the hexagon
r = Math.min(width, height) * 0.4;
var angle = 60;
for (var i = 1; i < 7; i++) {
var tempX = r * sin((angle * i + 30) % 360)
var tempY = r * cos((angle * i + 30) % 360)
points.push([tempX, tempY]);
}
background(0);
stroke(0, 0, 255);
//rectMode(CENTER);
}
function draw() {
background(0);
// draw the lines of ...
push();
translate(width/2, height/2);
rotate(frameCount * 0.75);
beginShape();
for (var i = 0; i < points.length; i++) {
// ... the hexagon perimeter
line(points[i][0], points[i][1], points[(i + 1) % 6][0], points[(i + 1) % 6][1]);
var tempAngle = 240 + i * 60;
var tempX = r * 1.1545 * sin(tempAngle) + points[i][0];
var tempY = r * 1.1545 * cos(tempAngle) + points[i][1];
for (var j = 0; j < lines + 1; j++) {
// ... the lines inside the hexagon
var tempAngle2 = tempAngle = (30 / lines * j) + 210 + i * 60;
var distance = r / cos(30 / lines * j);
var tempX2 = distance * sin(tempAngle2) + points[i][0];
var tempY2 = distance * cos(tempAngle2) + points[i][1];;
line(points[i][0], points[i][1], tempX2, tempY2);
}
endShape();
}
pop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.js"></script>

Ripples on image

How to achieve a similar effect on an image instead of raw canvas?
/**
* Water ripple effect.
* Original code (Java) by Neil Wallis
* #link http://www.neilwallis.com/java/water.html
*
* #author Sergey Chikuyonok (serge.che#gmail.com)
* #link http://chikuyonok.ru
*/
(function(){
var canvas = document.getElementById('c'),
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
mapind,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
* (see example here: http://media.chikuyonok.ru/ripple/)
* But I need to draw simple artwork to bypass 1k limitation
*/
with (ctx) {
fillStyle = '#a2ddf8';
fillRect(0, 0, width, height);
fillStyle = '#07b';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
restore();
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 512;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var i, a, b, data, cur_pixel, new_pixel, old_data;
i = oldind;
oldind = newind;
newind = i;
i = 0;
mapind = oldind;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_mapind = mapind,
_newind = newind,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind + i];
data -= data >> 5;
_ripplemap[_newind + i] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++_mapind;
++i;
}
}
mapind = _mapind;
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
disturb(rnd() * width, rnd() * height);
}, 700);
})();
<canvas id="c"></canvas>
Just look at this majestic unicorn: codepen. Data URI used to avoid CORS problems, it'll work with any number of images (all images by default).
JS:
window.addEventListener('load',()=>{
document.querySelectorAll('img').forEach((img)=>{
var cont = document.createElement('div');
cont.style.position = 'relative'
cont.style.display = 'inline-block'
img.parentNode.insertBefore(cont,img);
img.style.verticalAlign = 'top';
cont.appendChild(img)
console.dir(img)
var c = document.createElement('canvas');
c.width = img.clientWidth
c.height = img.clientHeight
c.style.position = 'absolute'
c.style.top = '0px'
c.style.left = '0px'
cont.appendChild(c)
console.log(c)
makeRipple(c,img)
})
})
function makeRipple(el,img){
var canvas = el,
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = img.clientWidth,
height = img.clientHeight,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
mapind,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
* (see example here: http://media.chikuyonok.ru/ripple/)
* But I need to draw simple artwork to bypass 1k limitation
*/
ctx.drawImage(img,0,0,img.clientWidth,img.clientHeight)
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
console.log('bbb')
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 512;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var i, a, b, data, cur_pixel, new_pixel, old_data;
i = oldind;
oldind = newind;
newind = i;
i = 0;
mapind = oldind;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_mapind = mapind,
_newind = newind,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind + i];
data -= data >> 5;
_ripplemap[_newind + i] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++_mapind;
++i;
}
}
mapind = _mapind;
}
canvas.onmousemove = function(/* Event */ evt) {
console.log('XXXX',evt.offsetX)
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
console.log('aaa')
disturb(rnd() * width, rnd() * height);
}, 700);
};

Plotting Points on Concentric Circles

I have an array of items that I would like to place on concentric circles. See diagram. I am having trouble with the math.
I can calculate the "steps" variable (each item is 40x40 so the steps are the circumference divided by the width plus margin). And I can calculate the points given the radius and the steps, but I don't know how to calculate the radius as a function of the current item index.
for(var i = 0; i < items.length; i++) {
var radius = functionOf(i)??;
var steps = Math.floor((2*radius*Math.PI)/60);
var x = Math.floor(0 + radius * Math.cos(2 * Math.PI * index / steps));
var y = Math.floor(0 + radius * Math.sin(2 * Math.PI * index / steps));
//draw item at x,y
}
Thoughts on how to calculate radius as a function of i?
You can calculate radius for every item using sum of arithmetic progression (for equidistant circles number of items form that progression), but there is simpler and faster approach - change radius after circle filling (pseudocode)
var radius = 0;
var i = 0;
while (i < items.length) {
var steps = Math.floor((2*radius*Math.PI)/60);
for(var index = 0; index < steps; index++) {
var x = Math.floor(0 + radius * Math.cos(2 * Math.PI * index / steps));
var y = Math.floor(0 + radius * Math.sin(2 * Math.PI * index / steps));
//draw item at x,y
i++;
if (i == items.length)
break;
}
radius = radius + 60; //start next circle
}
About arithmetic progression:
for progression with the first item a0 and difference d the sum of first n members is
S = n * (2 * a0 + d * (n - 1)) / 2
so to find what circle some index S belongs to, we have to solve quadratic inequality (find max integer x to fulfill the condition)
x^2 * d + x * (2 * a0 - d) - 2 * S <= 0
checked solution in Delphi:
function GetCircle(a0, d, i: Integer): Integer;
var
Discr: Double;
begin
Discr := (2 * a0 - d) * (2 * a0 - d) + 8 * i * d;
if Discr < 0 then
Exit(0);
Result := Floor((d - 2 * a0 + Sqrt(Discr)) / (2 * d));
end;
for case a0=1, d=6 (your picture differs a bit - progression is not exact) it gives
i=0: 0
i=1..7: 1
i=8..20: 2
i=21..39: 3
and so on
This means:
0th item is at the circle with radius 0
3rd item at the circle with radius 1 * R
11th item at the circle with radius 2 * R
...
Given your sample image, an easy approach is to add 6 elements for every outer circle, so that we have the sequence: 1, 6, 12, 18...
Then the radius of each circle can be enlarged by a constant amount, bigger then the element size:
// center of the circles
var centerx = 350;
var centery = 350;
// start drawing the central (first) element
if ( items.length > 0 ) {
// draw item at centerx, centery
}
var k = 1;
var i = 1;
while ( i < items.length ) {
// number of elements on this circle
var steps = k * 6;
// angular distance between elements
var angle_range = 2 * Math.PI / steps;
// every circle is bigger then the previuos of the same amount
var radius = k * 60;
var j = 0;
while ( j < steps && i < items.length ) {
var angle = j * angle_range;
var x = Math.round(centerx + radius * Math.cos(angle));
var y = Math.round(centery + radius * Math.sin(angle));
//draw item at x,y
i++;
j++;
}
k++;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Circles</title>
<link href="stile.css"/>
</head>
<body>
<canvas id="myCanvas" width="700" height="700" style="border:1px solid #000000;">
</canvas>
<script>
// dummy draw function
function draw_item( itm, cntx, x, y) {
cntx.fillStyle = "#0022FF";
cntx.fillRect(x - 20, y - 20, 40, 40);
cntx.font = "14px Arial";
cntx.fillStyle = "#FFFFFF";
cntx.fillText(itm, x - 7, y + 5);
}
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// dummy array
var items = [];
for ( var i = 0; i < 91; i++ ) {
items.push(i);
}
// center of the circles
var centerx = 350;
var centery = 350;
// start drawing the central (first) element
if ( items.length > 0 ) {
draw_item(items[0], ctx, centerx, centery);
}
var k = 1;
var i = 1;
while ( i < items.length ) {
// number of elements on this circle
var steps = k * 6;
// angular distance between elements
var angle_range = 2 * Math.PI / steps;
// every circle is bigger then the previuos of the same amount
var radius = k * 60;
var j = 0;
while ( j < steps && i < items.length ) {
var angle = j * angle_range;
var x = Math.round(centerx + radius * Math.cos(angle));
var y = Math.round(centery + radius * Math.sin(angle));
//draw item at x,y
draw_item(items[i], ctx, x, y);
i++;
j++;
}
k++;
}
</script>
</body>
</html>

Brownian motion simulation does not properly simulate particle collisions

I am trying to create a simulation for experimenting with the Brownian motion. The idea is to make numerous particles (points) each moving randomly and colliding with each other, while conserving the total energy. That, I did succeed programming.
Now the problematic part is to make these point-like particles collide with a big circle and program the collision correctly. The difference is I made the particles move towards a totally random angle after their collision with each other, but when they collide with the circle, their previous path and speed, together with the circle's path and speed determines the result. So what I tried to do is to convert to a coordinate system attached to the center of the circle, calculate the collision point of every particle, which entered into the circle in the previous step, then calculate the angle in which they arrived. Now then the momentum of the particle is decomposed into two components: the tangential component remains the same, while the radial component changes according to the laws of collisions. This affects the motion of the circle as well. After calculating all the collisions, we move back to the original reference system, and draw all the objects.
The code can be found below, and it seems to be broken. I guess I was mistaken when calculating with the angles, because some particles can be shown arriving to the circle at one point, then "exiting" through the other side of it. I just lost track in following it all. I'm a complete newbie to programming, so be... um... gentle, please. :) Another strange thing is that the circle trends to move to a direction it started to move early, which is not what it supposed to do.
The mass of the particles are given, the mass of the circle is calculated with it's radius.
You can find the simulation on this website:
http://sixy.uw.hu/brown
It is not finished yet, but the start button works fine. :)
The part of the code I'm asking about is here: (shall I give you the whole code?)
function animateParticles() {
if ($('N').value != particleCount || $('S').value != circle.R) {
reset();
}
switch (activeGraph) {
case '#graph':
chartOptions.title.text = 'Távolság';
chartOptions.axisY.title = 'd [px]';
break;
if (Date.now() > lastAnim + 22) {
context.clearRect(0, 0, canvas.width, canvas.height);
// Particles step and collide
for (var i in particle) {
// Particle steps
{
particle[i].x += particle[i].vx;
particle[i].y += particle[i].vy;
}
// If not in the circle
if ((circle.x - particle[i].x) * (circle.x - particle[i].x) + (circle.y - particle[i].y) * (circle.y - particle[i].y) > circle.R * circle.R) {
// Collides with some of the rest of the particles
for (var j = 1; j <= $('N').value; j += Math.ceil(particleCount / (Math.random() * 50 + 50))) {
// If that's not in the circle as well
if ((circle.x - particle[j].x) * (circle.x - particle[j].x) + (circle.y - particle[j].y) * (circle.y - particle[j].y) > circle.R * circle.R) {
// If not himself
if (j != i) {
if (particle[i].coll == 0) {
// Collide
if (Math.pow((particle[i].x - particle[j].x), 2) + Math.pow((particle[i].y - particle[j].y), 2) <= 4) {
var tkpx = (particle[i].vx + particle[j].vx) / 2;
var tkpy = (particle[i].vy + particle[j].vy) / 2;
var fi = Math.random() * 2 * Math.PI;
var index;
var ix = particle[i].vx;
var iy = particle[i].vy;
particle[i].vx = Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.cos(fi) + tkpx;
particle[i].vy = Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.sin(fi) + tkpy;
particle[j].vx = -Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.cos(fi) + tkpx;
particle[j].vy = -Math.sqrt(Math.pow((ix - tkpx), 2) + Math.pow((iy - tkpy), 2)) * Math.sin(fi) + tkpy;
}
}
}
if (particle[i].coll >= 1) {
particle[i].coll -= 1;
}
}
}
}
// Collision with the walls
{
if (particle[i].x >= canvas.width) {
var temp;
temp = particle[i].x - canvas.width;
particle[i].x = canvas.width - temp;
particle[i].vx *= -1
}
if (particle[i].x <= 0) {
var temp;
temp = -particle[i].x;
particle[i].x = temp;
particle[i].vx *= -1;
}
if (particle[i].y >= canvas.height) {
var temp;
temp = particle[i].y - canvas.height;
particle[i].y = canvas.height - temp;
particle[i].vy *= -1;
}
if (particle[i].y <= 0) {
var temp;
temp = -particle[i].y;
particle[i].y = temp;
particle[i].vy *= -1;
}
}
}
//console.log(circle);
// Circle steps and collides with particles
var cu = 0;
var cv = 0;
for (var i in particle) {
if ((circle.x - particle[i].x) * (circle.x - particle[i].x) + (circle.y - particle[i].y) * (circle.y - particle[i].y) < circle.R * circle.R) {
var pu;
var pv;
var px;
var py;
var px0;
var py0;
var t;
var t2;
var Tx;
var Ty;
var fi;
var p;
var q;
var cuu;
var cvv;
px = particle[i].x - circle.x;
py = particle[i].y - circle.y;
pu = particle[i].vx - circle.vx;
pv = particle[i].vy - circle.vy;
px0 = px - particle[i].vx;
py0 = py - particle[i].vy;
// Calculating the meeting point of the collision
t = (-(px0 * pu + py0 * pv) - Math.sqrt(circle.R * circle.R * (pu * pu + pv * pv) - Math.pow(px0 * pv - py0 * pu, 2))) / (pu * pu + pv * pv);
t2 = ((px - px0) * (px - px0) + (py - py0) * (py - py0)) / Math.sqrt(pu * pu + pv * pv) - t;
Tx = px0 + t * pu;
Ty = py0 + t * pv;
//console.log("TX: ", Tx);
//console.log("TY: ", Ty);
// Calculating the angle
{
if (Tx > 0 && Ty >= 0) {
fi = 2 * Math.PI - Math.atan(Ty / Tx);
}
else if (Tx < 0 && Ty >= 0) {
fi = Math.PI - Math.atan(Ty / Tx);
}
else if (Tx < 0 && Ty < 0) {
fi = Math.PI / 2 + Math.atan(Ty / Tx);
}
else if (Tx > 0 && Ty < 0) {
fi = -Math.atan(Ty / Tx);
}
else if (Tx = 0 && Ty >= 0) {
fi = 3 / 2 * Math.PI;
}
else if (Tx = 0 && Ty < 0) {
fi = Math.PI / 2;
}
}
//console.log("FI:", fi);
p = pu * Math.cos(fi) + pv * Math.sin(fi);
q = -pu * Math.sin(fi) + pv * Math.cos(fi);
cuu = 2 * q / (circle.M + 1);
cvv = 0;
q *= (1 - circle.M) / (1 + circle.M);
cu += cuu * Math.cos(-fi) + cvv * Math.sin(-fi);
cv += -cuu * Math.sin(-fi) + cvv * Math.cos(-fi);
//console.log("CU: ", cu);
//console.log("CV: ", cv);
pu = p * Math.cos(-fi) + q * Math.sin(-fi);
pv = -p * Math.sin(-fi) + q * Math.cos(-fi);
px = Tx + t2 * pu;
py = Ty + t2 * pv;
particle[i].x = px + circle.x;
particle[i].y = py + circle.y;
particle[i].vx = pu + circle.vx;
particle[i].vy = pv + circle.vy;
}
}
// Moving circle
{
circle.vx += cu;
circle.vy += cv;
circle.x += circle.vx;
circle.y += circle.vy;
for (var i in particle) {
while ((circle.x - particle[i].x) * (circle.x - particle[i].x) + (circle.y - particle[i].y) * (circle.y - particle[i].y) < circle.R * circle.R) {
particle[i].x += circle.vx / Math.abs(circle.vx);
particle[i].y += circle.vy / Math.abs(circle.vy);
}
}
circlePath.push({x: circle.x, y: circle.y});
if (Date.now() > lastDraw + 500) {
items.push({
y: Math.sqrt((circle.x - canvas.width / 2) * (circle.x - canvas.width / 2) + (circle.y - canvas.height / 2) * (circle.y - canvas.height / 2)),
x: (items.length + 1) / 2
});
drawChart(activeGraph);
lastDraw = Date.now();
}
}
//console.log(circle);
drawCircle();
for (var i in particle) drawParticle(particle[i]);
lastAnim = Date.now();
}
animation = window.requestAnimationFrame(animateParticles);
}

Categories