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);
}
Related
I've been struggling with this one for a couple of days now. I am trying to figure out how to make the arcs in this code rotate. I think the problem is that they are in a nested loop with random variables but I'm not sure how to fix that and still make it work.
I know I need to increase or decrease the start/stop angles in unison but to make them rotate but I need to figure out how to get static arcs once they are drawn first (i think).
Any ideas would be greatly appreciated.
let data;
let index = 0;
function preload() {
// data = loadJSON("palettes.json");
data = {
palettes: [
["#69d2e7", "#a7dbd8", "#e0e4cc", "#f38630", "#fa6900"]
]
}
}
function setup() {
createCanvas(windowWidth, windowHeight);
index = floor(random(data.palettes.length));
arcNumber = round(random(1, 3))
}
function draw() {
angleMode(DEGREES)
let palette = data.palettes[index];
let combBorderHeight = 0.2 * height
let combBorderWidth = 0.2 * width
let borderHeight = 0.1 * height
let borderWidth = 0.1 * width
stroke(palette[0])
background(palette[0])
for (let x = 0; x < 7; x++) {
for (let y = 0; y < 7; y++) {
if ((x % 2 == 0 && y % 2 == 0) || (x % 2 == 1 && y % 2 == 1)) {
fill(palette[1])
} else {
fill(palette[2])
}
rect((x + borderWidth + (x * (width - combBorderWidth) / 7)), (y + borderHeight + (y * (height - combBorderHeight) / 7)), (width - combBorderWidth) / 7, (height - combBorderWidth) / 7)
let arcNumber;
arcNumber = round(random(1, 3))
for (let i = 0; i < arcNumber; i++) {
let arcRand1 = random(10, random(0.4, 0.8) * (height - combBorderHeight) / 7)
let arcStart1 = random(0, 320)
let arcStop1 = random(arcStart1 + 40, 360)
let arcPalette = [palette[0], palette[3], palette[4]]
stroke(arcPalette[i])
push()
strokeCap(SQUARE)
strokeWeight(random(1, 10))
noFill()
arc((x + borderWidth + (x * (width - combBorderWidth) / 7) + 0.5 * (width - combBorderWidth) / 7), (y + borderHeight + (y * (height - combBorderHeight) / 7) + 0.5 * (height - combBorderHeight) / 7), arcRand1, arcRand1, arcStart1, arcStop1, OPEN, 50)
noLoop()
pop()
stroke(palette[0])
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.js" integrity="sha512-+tu0+vUXyZX+S51npa//IN6znNTLZsBoy8mXn9WzHxfBqYMy6gOzzfTK0VqZf9O171RY9AJviHDokCnvEq8+1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
If you remove the noLoop() function things will start to rotate randomly. You may want to store all those rotation variables in a 3D array that corresponds to each square (x,y) and arc(i).
let data;
let index = 0;
var my_size = 2;
var delta_angle = 0.1
var offset_angle = 0;
function preload() {
// data = loadJSON("palettes.json");
data = {
palettes: [
["#69d2e7", "#a7dbd8", "#e0e4cc", "#f38630", "#fa6900"]
]
}
}
function setup() {
createCanvas(windowWidth, windowHeight);
index = floor(random(data.palettes.length));
arcNumber = round(random(1, 3))
}
function draw() {
angleMode(DEGREES)
let palette = data.palettes[index];
let combBorderHeight = 0.2 * height
let combBorderWidth = 0.2 * width
let borderHeight = 0.1 * height
let borderWidth = 0.1 * width
stroke(palette[0])
background(palette[0])
offset_angle += delta_angle;
for (let x = 0; x < my_size; x++) {
for (let y = 0; y < my_size; y++) {
if ((x % 2 == 0 && y % 2 == 0) || (x % 2 == 1 && y % 2 == 1)) {
fill(palette[1])
} else {
fill(palette[2])
}
rect((x + borderWidth + (x * (width - combBorderWidth) / my_size)), (y + borderHeight + (y * (height - combBorderHeight) / my_size)), (width - combBorderWidth) / my_size, (height - combBorderWidth) / my_size)
let arcNumber;
arcNumber = round(random(1, 3))
for (let i = 0; i < arcNumber; i++) {
let arcRand1 = random(10, random(0.4, 0.8) * (height - combBorderHeight) / my_size)
let arcStart1 = random(0, 320)
let arcStop1 = random(arcStart1 + 40, 360)
let arcPalette = [palette[0], palette[3], palette[4]]
stroke(arcPalette[i])
push()
strokeCap(SQUARE)
strokeWeight(random(1, 10))
noFill()
arc((x + borderWidth + (x * (width - combBorderWidth) / my_size) + 0.5 * (width - combBorderWidth) / my_size), (y + borderHeight + (y * (height - combBorderHeight) / my_size) + 0.5 * (height - combBorderHeight) / my_size), arcRand1, arcRand1, arcStart1 + offset_angle, arcStop1 + offset_angle, OPEN, 50)
// noLoop()
pop()
stroke(palette[0])
}
}
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.js" integrity="sha512-+tu0+vUXyZX+S51npa//IN6znNTLZsBoy8mXn9WzHxfBqYMy6gOzzfTK0VqZf9O171RY9AJviHDokCnvEq8+1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
I am trying to make a raycasting game. Everything is rendered correctly except when the ray is facing up (angle > PI) or facing right(angle > 0.5PI and < 1.5PI) lines are drawn on walls. I am not sure what is causing it to happen but I know that the lines are not affected by the rotation of the player only by the players position. I tried rounding the rays position but that did not help.
I don't have enough reputation to post images.
Right and up ray walls.
(https://i.imgur.com/DKlfzM3.png)
The walls up close.
(https://i.imgur.com/orP89sT.png)
Left and down ray walls.
(https://i.imgur.com/YVggx51.png)
Code:
let rayX, rayY, rayAngle, rayDeltaX, rayDeltaY
for (let i = 0; i < this.screen.width; i ++) {
rayAngle = this.angle - this.fov / 2 + i * (this.fov / this.screen.width)
if (rayAngle < 0) {
rayAngle += Math.PI * 2
}
else if (rayAngle > Math.PI * 2) {
rayAngle -= Math.PI * 2
}
rayX = this.x
rayY = this.y
let stepY
if (rayAngle > Math.PI) {
stepY = -this.tileSize
rayY = Math.floor(rayY / this.tileSize) * this.tileSize - 1
}
else {
stepY = this.tileSize
rayY = Math.floor(rayY / this.tileSize) * this.tileSize + this.tileSize
}
rayX = this.x + (rayY - this.y) / Math.tan(rayAngle)
rayDeltaY = stepY
rayDeltaX = stepY / Math.tan(rayAngle)
while(true) {
if (this.Map.map[Math.floor(rayY / this.tileSize) * this.Map.width + Math.floor(rayX / this.tileSize)] == '#') {
break
}
rayX += rayDeltaX
rayY += rayDeltaY
}
let rayHorizontalX = rayX
let rayHorizontalY = rayY
let rayDistanceHorizontal = Math.sqrt((this.x - rayHorizontalX) ** 2 + (this.y - rayHorizontalY) ** 2)
rayX = this.x
rayY = this.y
let stepX
if (rayAngle > 0.5 * Math.PI && rayAngle < 1.5 * Math.PI) {
stepX = -this.tileSize
rayX = Math.floor(rayX / this.tileSize) * this.tileSize - 1
}
else {
stepX = this.tileSize
rayX = Math.floor(rayX / this.tileSize) * this.tileSize + this.tileSize
}
rayY = this.y + (rayX - this.x) * Math.tan(rayAngle)
rayDeltaY = stepX * Math.tan(rayAngle)
rayDeltaX = stepX
while(true) {
if (this.Map.map[Math.floor(rayY / this.tileSize) * this.Map.width + Math.floor(rayX / this.tileSize)] == '#') {
break
}
rayX += rayDeltaX
rayY += rayDeltaY
}
let rayVerticalX = rayX
let rayVerticalY = rayY
let rayDistanceVertical = Math.sqrt((this.x - rayVerticalX) ** 2 + (this.y - rayVerticalY) ** 2)
let rayFinalDistance
if (rayDistanceHorizontal < rayDistanceVertical) {
rayFinalDistance = rayDistanceHorizontal
ctx.fillStyle = 'darkblue'
}
else {
rayFinalDistance = rayDistanceVertical
ctx.fillStyle = 'blue'
}
let rayCorrectedDistance = rayFinalDistance * Math.cos(rayAngle - this.angle)
let lineHeight = this.tileSize * (this.screen.width / 2 / Math.tan(this.fov / 2)) / rayCorrectedDistance
let lineBottom = this.projectionPlane.centerY + lineHeight * 0.5
let lineTop = this.projectionPlane.height - lineBottom
ctx.fillRect(i, lineTop, 1, lineHeight)
}
Any help would be appreciated.
I fixed the problem. When the ray was facing up or right I was substracting 1 from the rounded position of the ray to get the first intersection. Substracting a smaller number like 0.001 fixed it.
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>
I'm currently working on a Pinball game using the HTML5 Canvas and JavaScript. Right now I'm getting a hard time with the pixel by pixel collision, which is fundamental because of the flippers.
Right now my Bounding Box Collision seems to be working
checkCollision(element) {
if (this.checkCollisionBoundingBox(element)) {
console.log("colision with the element bounding box");
if (this.checkCollisionPixelByPixel(element)) {
return true;
} else {
return false;
}
} else {
return false;
}
}
checkCollisionBoundingBox(element) {
if (this.pos.x < element.pos.x + element.width && this.pos.x + this.width > element.pos.x && this.pos.y < element.pos.y + element.height && this.pos.y + this.height > element.pos.y) {
return true;
} else {
return false;
}
}
I've tried several ways of implementing the pixel by pixel one but for some reason it does not work perfectly (on walls, on images, on sprites etc). I'll leave them here:
checkCollisionPixelByPixel(element) {
var x_left = Math.floor(Math.max(this.pos.x, element.pos.x));
var x_right = Math.floor(Math.min(this.pos.x + this.width, element.pos.x + element.width));
var y_top = Math.floor(Math.max(this.pos.y, element.pos.y));
var y_bottom = Math.floor(Math.min(this.pos.y + this.height, element.pos.y + element.height));
for (var y = y_top; y < y_bottom; y++) {
for (var x = x_left; x < x_right; x++) {
var x_0 = Math.round(x - this.pos.x);
var y_0 = Math.round(y - this.pos.y);
var n_pix = y_0 * (this.width * this.total) + (this.width * (this.actual-1)) + x_0; //n pixel to check
var pix_op = this.imgData.data[4 * n_pix + 3]; //opacity (R G B A)
var element_x_0 = Math.round(x - element.pos.x);
var element_y_0 = Math.round(y - element.pos.y);
var element_n_pix = element_y_0 * (element.width * element.total) + (element.width * (element.actual-1)) + element_x_0; //n pixel to check
var element_pix_op = element.imgData.data[4 * element_n_pix + 3]; //opacity (R G B A)
console.log(element_pix_op);
if (pix_op == 255 && element_pix_op == 255) {
console.log("Colision pixel by pixel");
/*Debug*/
/*console.log("This -> (R:" + this.imgData.data[4 * n_pix] + ", G:" + this.imgData.data[4 * n_pix + 1] + ", B:" + this.imgData.data[4 * n_pix + 2] + ", A:" + pix_op + ")");
console.log("Element -> (R:" + element.imgData.data[4 * element_n_pix] + ", G:" + element.imgData.data[4 * element_n_pix + 1] + ", B:" + element.imgData.data[4 * element_n_pix + 2] + ", A:" + element_pix_op + ")");
console.log("Collision -> (x:" + x + ", y:" + y +")");
console.log("This(Local) -> (x:" + x_0 + ", y:" + y_0+")");
console.log("Element(Local) -> (x:" + element_x_0 + ", y:" + element_y_0+")");*/
/*ball vector*/
var vector = {
x: (x_0 - Math.floor(this.imgData.width / 2)),
y: -(y_0 - Math.floor(this.imgData.height / 2))
};
//console.log("ball vector -> ("+vector.x+", "+vector.y+") , Angulo: "+ Math.atan(vector.y/vector.x)* 180/Math.PI);
// THIS WAS THE FIRST TRY, IT DIDN'T WORK WHEN THE BALL WAS GOING NORTHEAST AND COLLIDED WITH A WALL. DIDN'T WORK AT ALL WITH SPRITES
//this.angle = (Math.atan2(vector.y, vector.x) - Math.PI) * (180 / Math.PI);
// THIS WAS THE SECOND ATTEMPT, WORKS WORSE THAN THE FIRST ONE :/
//normal vector
var normal = {
x: (x_0 - (this.imgData.width / 2)),
y: -(y_0 - (this.imgData.height / 2))
};
//Normalizar o vetor
var norm = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
if (norm != 0) {
normal.x = normal.x / norm;
normal.y = normal.y / norm;
}
var n_rad = Math.atan2(normal.y, normal.x);
var n_deg = (n_rad + Math.PI) * 180 / Math.PI;
console.log("Vetor Normal -> (" + normal.x + ", " + normal.y + ") , Angulo: " + n_deg);
//Vetor Velocidade
var velocity = {
x: Math.cos((this.angle * Math.PI / 180) - Math.PI),
y: Math.sin((this.angle * Math.PI / 180) - Math.PI)
};
console.log("Vetor Velocidade -> (" + velocity.x + ", " + velocity.y + ") , Angulo: " + this.angle);
//Vetor Reflexao
var ndotv = normal.x * velocity.x + normal.y * velocity.y;
var reflection = {
x: -2 * ndotv * normal.x + velocity.x,
y: -2 * ndotv * normal.y + velocity.y
};
var r_rad = Math.atan2(reflection.y, reflection.x);
var r_deg = (r_rad + Math.PI) * 180 / Math.PI;
console.log("Vetor Reflexao -> (" + reflection.x + ", " + reflection.y + ") , Angulo: " + r_deg);
this.angle = r_deg;
return true;
}
}
}
return false;
}
}
The ball class
class Ball extends Element {
constructor(img, pos, width, height, n, sound, angle, speed) {
super(img, pos, width, height, n, sound);
this.angle = angle; //direction [0:360[
this.speed = speed;
}
move(ctx, cw, ch) {
var rads = this.angle * Math.PI / 180
var vx = Math.cos(rads) * this.speed / 60;
var vy = Math.sin(rads) * this.speed / 60;
this.pos.x += vx;
this.pos.y -= vy;
ctx.clearRect(0, 0, cw, ch);
this.draw(ctx, 1);
}
}
Assuming a "flipper" is composed of 2 arcs and 2 lines it would be much faster to do collision detection mathematically rather than by the much slower pixel-test method. Then you just need 4 math collision tests.
Even if your flippers are a bit more complicated than arcs+lines, the math hit tests would be "good enough" -- meaning in your fast-moving game, the user cannot visually notice the approximate math results vs the pixel-perfect results and the difference between the 2 types of tests will not affect gameplay at all. But the pixel-test version will take magnitudes more time and resources to accomplish. ;-)
First two circle-vs-circle collision tests:
function CirclesColliding(c1,c2){
var dx=c2.x-c1.x;
var dy=c2.y-c1.y;
var rSum=c1.r+c2.r;
return(dx*dx+dy*dy<=rSum*rSum);
}
Then two circle-vs-line-segment collision tests:
// [x0,y0] to [x1,y1] define a line segment
// [cx,cy] is circle centerpoint, cr is circle radius
function isCircleSegmentColliding(x0,y0,x1,y1,cx,cy,cr){
// calc delta distance: source point to line start
var dx=cx-x0;
var dy=cy-y0;
// calc delta distance: line start to end
var dxx=x1-x0;
var dyy=y1-y0;
// Calc position on line normalized between 0.00 & 1.00
// == dot product divided by delta line distances squared
var t=(dx*dxx+dy*dyy)/(dxx*dxx+dyy*dyy);
// calc nearest pt on line
var x=x0+dxx*t;
var y=y0+dyy*t;
// clamp results to being on the segment
if(t<0){x=x0;y=y0;}
if(t>1){x=x1;y=y1;}
return( (cx-x)*(cx-x)+(cy-y)*(cy-y) < cr*cr );
}
I need som help on moving the elements in the array I created call the "Predator" array. Now I have all the images appearing on the SVG box as well as stored in the array.
I have difficulties calling them in a "for" loop and to move them randomly. Further that, I would want to animate the images more closely to real life. Example pls take a look on this website.
<script>
var startPredator = 20; //number of fish to start with
var Predator = []; //array of fish
var predatorW = 307; //fish width
var predatorH = 313; //fish height
var velocity = 100; //base velocity
var imageStrip; //predator image strip
//var backgroundImage; //background image
//var backgroundImageW = 981; //background image width
//var backgroundImageH = 767; //background image height
//var WIDTH = document.body.offsetWidth;
//var HEIGHT = document.body.offsetHeight;
function animate(){
document.animation.src = arraylist[imgNum].src
imgNum++
for (var i=arraylist[].length; i--;){
arraylist[i].move();
}
setInterval(function () { draw(); }, 16.7);
}
function start() {
for (var i = 0; i < arraylist.length; i++) {
var image = document.createElement("img");
image.alt = images[i][0];
image.width = "150";
image.height = "150";
image.src = images[i][1];
var j = i;
if (j > 2) j = i - 3;
document.getElementsByTagName("div")[j].appendChild(image);
}
}
function Predator() {
var angle = Math.PI * 2 * Math.random(); //set the x,y direction this predator swims
var xAngle = Math.cos(angle); //set the x value of the angle
var yAngle = Math.sin(angle); //set the y value of the angle
var zAngle = 1+-2*Math.round(Math.random()); //set if the predator is swimming toward us or away. 1 = toward us; -1 = away from us
var x = Math.floor(Math.random() * (WIDTH - predatorW) + predatorW / 2); //set the starting x location
var y = Math.floor(Math.random() * (HEIGHT - predatorH) + predatorH / 2); //set the starting y location
var zFar = 100; //set how far away can a predator go
var zFarFactor = 1; //set the max size the predator can be. 1=100%
var zClose = 0; //set how near a predator can come
var z = Math.floor(Math.random() * ((zFar - zClose))); //set the starting z location
var scale = .1; //set the rate of scaling each frame
var flip = 1; //set the direction of the fish. 1=right; -1=left
var cellCount = 16; //set the number of cells (columns) in the image strip animation
var cell = Math.floor(Math.random() * (cellCount-1)); //set the first cell (columns) of the image strip animation
var cellReverse = -1; //set which direction we go through the image strip
var species = Math.floor(Math.random() * 3); //set which species of predator this predator is. each species is a row in the image strip
// stop predator from swimming straight up or down
if (angle > Math.PI * 4 / 3 && angle < Math.PI * 5 / 3 || angle > Math.PI * 1 / 3 && angle < Math.PI * 2 / 3) {
angle = Math.PI * 1 / 3 * Math.random();
xAngle = Math.cos(angle);
yAngle = Math.sin(angle);
}
// face the predator the right way if angle is between 6 o'clock and 12 o'clock
if (angle > Math.PI / 2 && angle < Math.PI / 2 * 3) {
flip = -1;
}
function swim() {
// Calculate next position of species
var nextX = x + xAngle * velocity * fpsMeter.timeDeltaS;
var nextY = y + yAngle * velocity * fpsMeter.timeDeltaS;
var nextZ = z + zAngle * .1 * velocity * fpsMeter.timeDeltaS;
var nextScale = Math.abs(nextZ) / (zFar - zClose);
// If species is going to move off right side of screen
if (nextX + fishW / 2 * scale > WIDTH) {
// If angle is between 3 o'clock and 6 o'clock
if ((angle >= 0 && angle < Math.PI / 2)) {
angle = Math.PI - angle;
xAngle = Math.cos(angle);
yAngle = Math.sin(angle) * Math.random();
flip = -flip;
}
// If angle is between 12 o'clock and 3 o'clock
else if (angle > Math.PI / 2 * 3) {
angle = angle - (angle - Math.PI / 2 * 3) * 2
xAngle = Math.cos(angle);
yAngle = Math.sin(angle) * Math.random();
flip = -flip;
}
}
// If fish is going to move off left side of screen
if (nextX - fishW / 2 * scale < 0) {
// If angle is between 6 o'clock and 9 o'clock
if ((angle > Math.PI / 2 && angle < Math.PI)) {
angle = Math.PI - angle;
xAngle = Math.cos(angle);
yAngle = Math.sin(angle) * Math.random();
flip = -flip;
}
// If angle is between 9 o'clock and 12 o'clock
else if (angle > Math.PI && angle < Math.PI / 2 * 3) {
angle = angle + (Math.PI / 2 * 3 - angle) * 2
xAngle = Math.cos(angle);
yAngle = Math.sin(angle) * Math.random();
flip = -flip;
}
}
// If fish is going to move off bottom side of screen
if (nextY + fishH / 2 * scale > HEIGHT) {
// If angle is between 3 o'clock and 9 o'clock
if ((angle > 0 && angle < Math.PI)) {
angle = Math.PI * 2 - angle;
xAngle = Math.cos(angle);
yAngle = Math.sin(angle) * Math.random();
}
}
// If fish is going to move off top side of screen
if (nextY - fishH / 2 * scale < 0) {
// If angle is between 9 o'clock and 3 o'clock
if ((angle > Math.PI && angle < Math.PI * 2)) {
angle = angle - (angle - Math.PI) * 2;
xAngle = Math.cos(angle);
yAngle = Math.sin(angle);
}
}
// If fish is going too far (getting too small)
if (nextZ <= zClose && zAngle < 0) {
zAngle = -zAngle;
}
// If fish is getting to close (getting too large)
if (((WIDTH / fishW) * 10) < ((fishW * fish.length) / WIDTH)) {
zFarFactor = .3
}
else if (((WIDTH / fishW) * 2) < ((fishW * fish.length) / WIDTH)) {
zFarFactor = .5
}
else { zFarFactor = 1 }
if (nextZ >= zFar * zFarFactor && zAngle > 0) {
zAngle = -zAngle;
}
if (scale < .1) { scale = .1 }; //don't let fish get too tiny
//draw the fish
//locate the fish
ctx.save();
ctx.translate(x, y);
ctx.scale(scale, scale); // make the fish bigger or smaller depending on how far away it is.
ctx.transform(flip, 0, 0, 1, 0, 0); //make the fish face the way he's swimming.
ctx.drawImage(imageStrip, fishW * cell, fishH * species, fishW, fishH, -fishW / 2, -fishH / 2, fishW, fishH); //draw the fish
ctx.save();
scale = nextScale // increment scale for next time
ctx.restore();
ctx.restore();
//increment to next state
x = nextX;
y = nextY;
z = nextZ;
if (cell >= cellCount-1 || cell <= 0) { cellReverse = cellReverse * -1; } //go through each cell in the animation
cell = cell + 1 * cellReverse; //go back down once we hit the end of the animation
}
return {
swim: swim
}
}
</script>
My concern is more on the animate and start function. Am I calling the images from the array to move?
Please offer a helping hand. Thanks