Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Electric Field Visualizer</title>
<style>
body { margin: 0; overflow: hidden; background-color: #050505; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
#ui-panel {
position: absolute; top: 20px; left: 20px; width: 280px;
background: rgba(20, 20, 20, 0.85); color: white; padding: 15px;
border-radius: 8px; border: 1px solid #444; backdrop-filter: blur(5px);
}
.control-group { margin-bottom: 15px; }
label { display: block; font-size: 0.9em; margin-bottom: 5px; color: #aaa; }
input[type="range"] { width: 100%; cursor: pointer; }
button {
width: 100%; padding: 8px; margin-top: 5px; cursor: pointer;
background: #3498db; border: none; color: white; border-radius: 4px; transition: 0.3s;
}
button:hover { background: #2980b9; }
button.secondary { background: #e74c3c; }
button.secondary:hover { background: #c0392b; }
.info { font-size: 0.8em; line-height: 1.4; color: #ccc; margin-top: 10px; border-top: 1px solid #444; pt: 10px; }
</style>
</head>
<body>
<div id="ui-panel">
<h3>Electric Field Lab</h3>
<div class="control-group">
<label>Add Charge</label>
<button onclick="addCharge(1)">+ Positive Charge</button>
<button class="secondary" onclick="addCharge(-1)">- Negative Charge</button>
</div>
<div class="control-group">
<label>Particle Density</label>
<input type="range" id="density" min="100" max="2000" value="800">
</div>
<div class="control-group">
<button onclick="clearScene()">Clear All Charges</button>
</div>
<div class="info">
<strong>Physics Rules:</strong><br>
• Field lines point <b>away</b> from positive (+q)<br>
• Field lines point <b>toward</b> negative (-q)<br>
• Intensity follows Inverse Square Law: <br>
<center><i>E = kQ / r²</i></center>
</div>
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- Configuration ---
let scene, camera, renderer, controls;
let charges = []; // Array to store {mesh, strength}
let fieldParticles;
const particleCount = 1500;
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const velocities = new Float32Array(particleCount * 3);
const k = 50; // Coulomb's constant approximation for visualization
init();
animate();
function init() {
// Scene setup
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(20, 20, 20);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// Grid & Lights
const gridHelper = new THREE.GridHelper(50, 50, 0x333333, 0x222222);
scene.add(gridHelper);
const light = new THREE.PointLight(0xffffff, 1000);
light.position.set(10, 20, 10);
scene.add(light);
scene.add(new THREE.AmbientLight(0x404040));
// Create Particle System for Field Lines
const geometry = new THREE.BufferGeometry();
for (let i = 0; i < particleCount; i++) {
resetParticle(i);
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.15,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
fieldParticles = new THREE.Points(geometry, material);
scene.add(fieldParticles);
// Default: A Dipole (One + and One -)
addCharge(1, -5, 2, 0);
addCharge(-1, 5, 2, 0);
window.addEventListener('resize', onWindowResize);
}
function addCharge(strength, x = 0, y = 2, z = 0) {
const geo = new THREE.SphereGeometry(0.5, 32, 32);
const mat = new THREE.MeshPhongMaterial({
color: strength > 0 ? 0xff3333 : 0x3333ff,
emissive: strength > 0 ? 0x440000 : 0x000044
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x || (Math.random() - 0.5) * 10, y, z || (Math.random() - 0.5) * 10);
scene.add(mesh);
charges.push({ mesh, strength });
}
window.addCharge = addCharge; // Expose to HTML
window.clearScene = () => {
charges.forEach(c => scene.remove(c.mesh));
charges = [];
};
function resetParticle(i) {
// Randomly place particle in the volume
positions[i * 3] = (Math.random() - 0.5) * 40;
positions[i * 3 + 1] = (Math.random() - 0.5) * 40;
positions[i * 3 + 2] = (Math.random() - 0.5) * 40;
}
function calculateField(x, y, z) {
let field = new THREE.Vector3(0, 0, 0);
charges.forEach(charge => {
let p = charge.mesh.position;
let r = new THREE.Vector3(x - p.x, y - p.y, z - p.z);
let distSq = r.lengthSq();
if (distSq < 0.5) return; // Prevent infinity near charge
let mag = (k * charge.strength) / distSq;
field.add(r.normalize().multiplyScalar(mag));
});
return field;
}
function animate() {
requestAnimationFrame(animate);
const posAttr = fieldParticles.geometry.attributes.position;
const colAttr = fieldParticles.geometry.attributes.color;
for (let i = 0; i < particleCount; i++) {
let x = posAttr.array[i * 3];
let y = posAttr.array[i * 3 + 1];
let z = posAttr.array[i * 3 + 2];
let force = calculateField(x, y, z);
// Move particle along field lines
posAttr.array[i * 3] += force.x * 0.1;
posAttr.array[i * 3 + 1] += force.y * 0.1;
posAttr.array[i * 3 + 2] += force.z * 0.1;
// Color based on field strength
let speed = force.length();
colAttr.array[i * 3] = Math.min(speed, 1.0); // R
colAttr.array[i * 3 + 1] = 0.2; // G
colAttr.array[i * 3 + 2] = Math.max(0.5 - speed, 0); // B
// Reset if it gets too far or hits a charge
let distFromOrigin = Math.sqrt(x*x + y*y + z*z);
if (distFromOrigin > 30 || speed < 0.001 || speed > 5) {
resetParticle(i);
}
}
posAttr.needsUpdate = true;
colAttr.needsUpdate = true;
controls.update();
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
3
2
266KB
1280KB
388.0ms
124.0ms
389.0ms