<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="700" height="500"></canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function vec3_add(a, b) {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
}
function vec3_sub(a, b) {
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}
function vec3_scale(v, s) {
return [v[0] * s, v[1] * s, v[2] * s];
}
function vec3_magnitude(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
function vec3_normalize(v) {
const mag = vec3_magnitude(v);
if (mag === 0) return [0, 0, 0];
return [v[0] / mag, v[1] / mag, v[2] / mag];
}
const charges = [{
pos: [250, 250, 0],
id: "negative"
},
{
pos: [450, 250, 0],
id: "positive"
}
];
const lines = 128;
const step_size = 4;
const max_steps = 1000;
function calculate_field_at_point(point, charges) {
let field = [0, 0, 0];
for (let charge of charges) {
let dir = vec3_sub(charge.pos, point);
let dist = vec3_magnitude(dir);
if (dist < 5) continue;
let strength = charge.id.includes("negative") ? 1 : -1;
strength /= (dist * dist);
field = vec3_add(field, vec3_scale(dir, strength));
}
return vec3_normalize(field);
}
function rk4_step(point, charges) {
let k1 = calculate_field_at_point(point, charges);
let temp = vec3_add(point, vec3_scale(k1, step_size * 0.5));
let k2 = calculate_field_at_point(temp, charges);
temp = vec3_add(point, vec3_scale(k2, step_size * 0.5));
let k3 = calculate_field_at_point(temp, charges);
temp = vec3_add(point, vec3_scale(k3, step_size));
let k4 = calculate_field_at_point(temp, charges);
return vec3_scale(
vec3_add(
vec3_add(
vec3_scale(k1, 1 / 6),
vec3_scale(k2, 1 / 3)
),
vec3_add(
vec3_scale(k3, 1 / 3),
vec3_scale(k4, 1 / 6)
)
),
step_size
);
}
function generate_circle_points(center, radius, num_points) {
let points = [];
for (let i = 0; i < num_points; i++) {
let angle = (i / num_points) * Math.PI * 2.0;
let x = center[0] + radius * Math.cos(angle);
let y = center[1] + radius * Math.sin(angle);
points.push([x, y, 0]);
}
return points;
}
function integrate_field_line(start_point, charges) {
let points = [start_point];
let current_point = [...start_point];
for (let i = 0; i < max_steps; i++) {
let step = rk4_step(current_point, charges);
if (vec3_magnitude(step) < 0.1) break;
current_point = vec3_add(current_point, step);
points.push([...current_point]);
let too_close = charges.some(charge => vec3_magnitude(vec3_sub(current_point, charge.pos)) < 15);
// if (too_close) break;
// if (current_point[0] < 0 || current_point[0] > canvas.width ||
// current_point[1] < 0 || current_point[1] > canvas.height) break;
}
return points;
}
const positive_charges = charges.filter(c => c.id.includes("positive"));
for (let charge of positive_charges) {
let start_points = generate_circle_points(charge.pos, 35, lines);
for (let start_point of start_points) {
let points = integrate_field_line(start_point, charges);
if (points.length > 5) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
ctx.lineWidth = 2;
ctx.stroke();
}
}
}
ctx.beginPath();
ctx.arc(charges[0].pos[0], charges[0].pos[1], 30, 0, Math.PI * 2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(charges[0].pos[0] - 15, charges[0].pos[1]);
ctx.lineTo(charges[0].pos[0] + 15, charges[0].pos[1]);
ctx.lineWidth = 5;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.beginPath();
ctx.arc(charges[1].pos[0], charges[1].pos[1], 30, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(charges[1].pos[0] - 15, charges[1].pos[1]);
ctx.lineTo(charges[1].pos[0] + 15, charges[1].pos[1]);
ctx.moveTo(charges[1].pos[0], charges[1].pos[1] - 15);
ctx.lineTo(charges[1].pos[0], charges[1].pos[1] + 15);
ctx.lineWidth = 5;
ctx.strokeStyle = "black";
ctx.stroke();
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="700" height="500"></canvas>
<script>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function vec3_add(a, b) {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
}
function vec3_sub(a, b) {
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
}
function vec3_scale(v, s) {
return [v[0] * s, v[1] * s, v[2] * s];
}
function vec3_magnitude(v) {
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
function vec3_normalize(v) {
const mag = vec3_magnitude(v);
if (mag === 0) return [0, 0, 0];
return [v[0] / mag, v[1] / mag, v[2] / mag];
}
const charges = [{
pos: [250, 250, 0],
id: "negative"
},
{
pos: [450, 250, 0],
id: "positive"
}
];
const lines = 128;
const step_size = 4;
const max_steps = 1000;
function calculate_field_at_point(point, charges) {
let field = [0, 0, 0];
for (let charge of charges) {
let dir = vec3_sub(charge.pos, point);
let dist = vec3_magnitude(dir);
if (dist < 5) continue;
let strength = charge.id.includes("negative") ? 1 : -1;
strength /= (dist * dist);
field = vec3_add(field, vec3_scale(dir, strength));
}
return vec3_normalize(field);
}
function rk4_step(point, charges) {
let k1 = calculate_field_at_point(point, charges);
let temp = vec3_add(point, vec3_scale(k1, step_size * 0.5));
let k2 = calculate_field_at_point(temp, charges);
temp = vec3_add(point, vec3_scale(k2, step_size * 0.5));
let k3 = calculate_field_at_point(temp, charges);
temp = vec3_add(point, vec3_scale(k3, step_size));
let k4 = calculate_field_at_point(temp, charges);
return vec3_scale(
vec3_add(
vec3_add(
vec3_scale(k1, 1 / 6),
vec3_scale(k2, 1 / 3)
),
vec3_add(
vec3_scale(k3, 1 / 3),
vec3_scale(k4, 1 / 6)
)
),
step_size
);
}
function generate_circle_points(center, radius, num_points) {
let points = [];
for (let i = 0; i < num_points; i++) {
let angle = (i / num_points) * Math.PI * 2.0;
let x = center[0] + radius * Math.cos(angle);
let y = center[1] + radius * Math.sin(angle);
points.push([x, y, 0]);
}
return points;
}
function integrate_field_line(start_point, charges) {
let points = [start_point];
let current_point = [...start_point];
for (let i = 0; i < max_steps; i++) {
let step = rk4_step(current_point, charges);
if (vec3_magnitude(step) < 0.1) break;
current_point = vec3_add(current_point, step);
points.push([...current_point]);
let too_close = charges.some(charge => vec3_magnitude(vec3_sub(current_point, charge.pos)) < 15);
// if (too_close) break;
// if (current_point[0] < 0 || current_point[0] > canvas.width ||
// current_point[1] < 0 || current_point[1] > canvas.height) break;
}
return points;
}
const positive_charges = charges.filter(c => c.id.includes("positive"));
for (let charge of positive_charges) {
let start_points = generate_circle_points(charge.pos, 35, lines);
for (let start_point of start_points) {
let points = integrate_field_line(start_point, charges);
if (points.length > 5) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
ctx.lineWidth = 2;
ctx.stroke();
}
}
}
ctx.beginPath();
ctx.arc(charges[0].pos[0], charges[0].pos[1], 30, 0, Math.PI * 2);
ctx.fillStyle = "blue";
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(charges[0].pos[0] - 15, charges[0].pos[1]);
ctx.lineTo(charges[0].pos[0] + 15, charges[0].pos[1]);
ctx.lineWidth = 5;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.beginPath();
ctx.arc(charges[1].pos[0], charges[1].pos[1], 30, 0, Math.PI * 2);
ctx.fillStyle = "red";
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = "black";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(charges[1].pos[0] - 15, charges[1].pos[1]);
ctx.lineTo(charges[1].pos[0] + 15, charges[1].pos[1]);
ctx.moveTo(charges[1].pos[0], charges[1].pos[1] - 15);
ctx.lineTo(charges[1].pos[0], charges[1].pos[1] + 15);
ctx.lineWidth = 5;
ctx.strokeStyle = "black";
ctx.stroke();
</script>
</body>
</html>
I am trying to simulate an electric field between two charged particles. To do I sample points around the positive charge, take a series of small steps and approximate the direction of the electric field each time using Runge–Kutta.
However in my snippet the field lines cannot reach behind the negative charge on the left.
I tried generating lines from negative to positive charges but lines start colliding and it looks ugly.
Basically this is what I have:
And what I want:
Share Improve this question edited Mar 10 at 23:01 imad asked Mar 10 at 15:31 imadimad 3714 silver badges13 bronze badges 6- Also, it seems the formulas are not correct. The field lines look like perfect circle. – Shahram Alemzadeh Commented Mar 10 at 16:28
- Shouldn't the arrows flow from negative to positive? – Tim Roberts Commented Mar 10 at 21:58
- @TimRoberts "Field lines due to stationary charges always originate from positive charges and terminate at negative charges" en.wikipedia./wiki/Electric_field – imad Commented Mar 10 at 22:21
- That convention for field lines is arbitrary. As others have said the problem is that some of the field lines that would reach the back side of the negative charge go off the canvas before they reach it. BTW simple charge configurations like these can be more easily dealt with analytically using complex number theory. – Martin Brown Commented Mar 11 at 9:46
- 1 Turns out to be harder than I expected to find a decent reference to this old school analytical trick online. This is the least confusing that I could find Electrostatics by Complex Variable methods II at Virginia State. Older books on mathematical methods of theoretical physics should have a chapter on it. You may need to look at part 1 first depending on your maths level. – Martin Brown Commented Mar 12 at 13:37
2 Answers
Reset to default 0For reasons I don't understand I get 'User does not have permission to comment on this post' so I put what is essentially a comment into this answer.
Your canvas is too small for the points you are trying to plot. Put a console.log just before the break where you have tested
(current_point[0] < 0 || current_point[0] > canvas.width ||
current_point[1] < 0 || current_point[1] > canvas.height)
Try commenting out the break and see the result.
What's happpening in your code is that you stop plotting a line immediately one point on it goes off the canvas, but you want to plot the remaining points on the line which lie within the canvas, so don't break out of the plotting.
I think in a general case you might need to draw field lines for all particles. It might not be good looking in some cases but at least guarantees all particles have a complete set of field lines.
It was difficult for me to edit your code, so I wrote a new one.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>79498480/how-to-properly-simulate-electric-field-lines</title>
<style>
canvas {
border: 1px solid;
display: block;
margin: auto;
}
</style>
<script>
const lines = 20;
const step_size = 5;
const max_steps = 1000;
const radius = 15;
const charges = [
{ x: 300, y: 350, q: 1 },
{ x: 700, y: 250, q: -1.8 },
{ x: 900, y: 470, q: 1.5 },
{ x: 1250, y: 300, q: -1.3 },
];
// two equal positvie charges
/*
const charges = [
{ x: 400, y: 350, q: 1 },
{ x: 1000, y: 350, q: 1 },
];
*/
// two equal charges - positive and negative
/*
const charges = [
{ x: 400, y: 350, q: 1 },
{ x: 1000, y: 350, q: -1 },
];
*/
window.addEventListener("load", function () {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
charges.forEach(charge => {
for (var i = 0; i < lines; i++) {
const sign = charge.q > 0 ? 1 : -1; // change the direction of field lines for negative charges from inward to outward
var x = charge.x + radius * Math.cos(2 * Math.PI * i / lines);
var y = charge.y + radius * Math.sin(2 * Math.PI * i / lines);
ctx.beginPath();
ctx.moveTo(x, y);
for (var j = 0; j < max_steps; j++) {
var Ex = 0; // x component of electric field
var Ey = 0; // y component of electric field
charges.forEach(charge => {
const deltax = x - charge.x;
const deltay = y - charge.y;
const r = Math.sqrt((deltax * deltax) + (deltay * deltay));
const E = sign * charge.q / Math.pow(r, 3);
Ex += E * deltax;
Ey += E * deltay;
});
const E = Math.sqrt(Ex * Ex + Ey * Ey);
x += step_size * Ex / E;
y += step_size * Ey / E;
ctx.lineTo(x, y);
}
ctx.stroke();
}
});
charges.forEach(charge => {
//draw a placeholder for each charge in 3 step_sizes:
//1- clear field lines in a radius scaled to the charge amount + 5px clearing ring
ctx.beginPath();
ctx.arc(charge.x, charge.y, Math.abs(charge.q * radius) + 5, 0, 2 * Math.PI);
ctx.fillStyle = "white";
ctx.fill();
//2- draw a circle with a radius scaled the charge and in color according to sign of charge
ctx.beginPath();
ctx.arc(charge.x, charge.y, Math.abs(charge.q * radius), 0, 2 * Math.PI);
ctx.fillStyle = charge.q > 0 ? "red" : "blue";
ctx.fill();
//3- write the amount of charge in the center of placeholder
ctx.fillStyle = "white";
ctx.font = "1.2em tahoma";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(charge.q, charge.x, charge.y);
});
});
</script>
</head>
<body>
<canvas id="canvas" width="1400px" height="700px"></canvas>
</body>
</html>
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744837887a4596375.html
评论列表(0条)