<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>3D Vector Field Simulator</title>
   <!-- Tailwind CSS for styling -->
   <script src="https://cdn.tailwindcss.com"></script>
   <!-- three.js for 3D rendering -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
   <!-- math.js for formula parsing -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/11.5.0/math.js"></script>
   <!-- ES6 Import Map for three.js addons -->
   <script type="importmap">
       {
           "imports": {
               "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js",
               "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/"
           }
       }
   </script>
   <link rel="preconnect" href="https://fonts.googleapis.com">
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
   <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code&display=swap" rel="stylesheet">
   <style>
       body {
           font-family: 'Inter', sans-serif;
       }
       .fira-code {
           font-family: 'Fira Code', monospace;
       }
       /* Style for formula inputs */
       input.formula-input {
           background-color: #374151; /* bg-gray-700 */
           border: 1px solid #4b5563; /* border-gray-600 */
           color: white;
           border-radius: 0.375rem; /* rounded-md */
           padding: 0.5rem 0.75rem;
           width: 100%;
           transition: border-color 0.2s, box-shadow 0.2s;
       }
       input.formula-input:focus {
           outline: none;
           border-color: #3b82f6; /* border-blue-500 */
           box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px #3b82f6;
       }
       input.formula-input.border-red-500 {
            border-color: #ef4444;
       }
       input.formula-input.border-red-500:focus {
           box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px #ef4444;
       }
   </style>
</head>
<body class="bg-gray-900 text-gray-200 flex flex-col items-center justify-center min-h-screen p-4">

   <!-- Main container for the simulation and UI -->
   <div class="flex flex-col items-center">
       <!-- 3D Display element (1000x1000) -->
       <div id="simulation-container" class="w-[1000px] h-[1000px] bg-black rounded-t-lg shadow-2xl overflow-hidden relative border-2 border-gray-700">
           <!-- 3D Renderer will attach its canvas here -->
       </div>

       <!-- UI control element (1000x300) -->
       <div id="ui-container" class="w-[1000px] h-[300px] bg-gray-800/50 backdrop-blur-sm rounded-b-lg shadow-2xl p-6 flex flex-col justify-between border-x-2 border-b-2 border-gray-700">
           
           <!-- Top section: Title -->
           <div class="flex justify-between items-start">
               <div>
                   <h1 class="text-2xl font-bold text-white">3D Vector Field Simulator</h1>
                   <p class="text-gray-400">Define the vector field F(x, y, z) = <i, j, k>.</p>
               </div>
           </div>

           <!-- Middle section: Vector component inputs -->
           <div class="flex-grow flex items-center justify-center gap-6">
               <div class="flex items-center gap-3">
                   <label for="i-comp" class="text-2xl fira-code text-gray-400">i =</label>
                   <input id="i-comp" type="text" class="formula-input fira-code text-lg w-64" value="-y">
               </div>
                <div class="flex items-center gap-3">
                   <label for="j-comp" class="text-2xl fira-code text-gray-400">j =</label>
                   <input id="j-comp" type="text" class="formula-input fira-code text-lg w-64" value="x">
               </div>
                <div class="flex items-center gap-3">
                   <label for="k-comp" class="text-2xl fira-code text-gray-400">k =</label>
                   <input id="k-comp" type="text" class="formula-input fira-code text-lg w-64" value="1">
               </div>
           </div>
            <p id="error-message" class="text-red-400 h-4 text-center"></p>

           <!-- Bottom section: Controls and Simulate Button -->
           <div class="flex justify-between items-end">
               <div class="flex gap-6">
                   <div>
                       <label for="density" class="text-sm text-gray-400">Grid Density</label>
                       <input id="density" type="range" min="3" max="15" value="10" class="w-48">
                   </div>
                    <div>
                       <label for="scale" class="text-sm text-gray-400">Vector Scale</label>
                       <input id="scale" type="range" min="0.1" max="3" value="0.8" step="0.1" class="w-48">
                   </div>
               </div>
               <button id="simulate-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-8 rounded-lg transition-colors shadow-lg text-lg">
                   Simulate
               </button>
           </div>
       </div>
   </div>

   <script type="module">
       // Import necessary three.js modules
       import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

       // --- State Management ---
       let scene, camera, renderer, controls, vectorFieldGroup;

       // --- DOM Element References ---
       const simContainer = document.getElementById('simulation-container');
       const iCompInput = document.getElementById('i-comp');
       const jCompInput = document.getElementById('j-comp');
       const kCompInput = document.getElementById('k-comp');
       const densitySlider = document.getElementById('density');
       const scaleSlider = document.getElementById('scale');
       const simulateButton = document.getElementById('simulate-button');
       const errorMessage = document.getElementById('error-message');

       // --- Initializer ---
       function initialize() {
           init3D();
           setupEventListeners();
           createVectorField();
       }

       // --- Event Setup ---
       function setupEventListeners() {
           simulateButton.addEventListener('click', createVectorField);
           window.addEventListener('resize', onWindowResize);
       }
       
       // --- 3D Scene Initialization ---
       function init3D() {
           const width = simContainer.clientWidth;
           const height = simContainer.clientHeight;
           
           scene = new THREE.Scene();
           scene.background = new THREE.Color(0x000000);

           camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
           camera.position.set(10, 10, 15);
           
           renderer = new THREE.WebGLRenderer({ antialias: true });
           renderer.setSize(width, height);
           simContainer.appendChild(renderer.domElement);
           
           scene.add(new THREE.AmbientLight(0xcccccc, 0.8));
           const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
           directionalLight.position.set(20, 30, 10);
           scene.add(directionalLight);
           
           const axesHelper = new THREE.AxesHelper(10);
           scene.add(axesHelper);

           controls = new OrbitControls(camera, renderer.domElement);
           controls.enableDamping = true;
           
           animate3D();
       }
       
       function onWindowResize() {
           const width = simContainer.clientWidth;
           const height = simContainer.clientHeight;
           camera.aspect = width / height;
           camera.updateProjectionMatrix();
           renderer.setSize(width, height);
       }

       // --- Vector Field Logic ---
       function createVectorField() {
           errorMessage.textContent = '';
           [iCompInput, jCompInput, kCompInput].forEach(el => el.classList.remove('border-red-500'));
           
           if (vectorFieldGroup) {
               scene.remove(vectorFieldGroup);
           }
           vectorFieldGroup = new THREE.Group();

           let iNode, jNode, kNode;
           try {
               iNode = math.parse(iCompInput.value).compile();
               jNode = math.parse(jCompInput.value).compile();
               kNode = math.parse(kCompInput.value).compile();
           } catch (err) {
                errorMessage.textContent = `Error parsing formula: ${err.message}`;
                if (err.message.includes(iCompInput.value)) iCompInput.classList.add('border-red-500');
                if (err.message.includes(jCompInput.value)) jCompInput.classList.add('border-red-500');
                if (err.message.includes(kCompInput.value)) kCompInput.classList.add('border-red-500');
                return;
           }

           const density = parseInt(densitySlider.value);
           const scale = parseFloat(scaleSlider.value);
           const gridSize = 20;
           const step = gridSize / (density -1);

           let maxMagnitude = 0;
           const vectors = [];

           for (let x = -gridSize / 2; x <= gridSize / 2; x += step) {
               for (let y = -gridSize / 2; y <= gridSize / 2; y += step) {
                   for (let z = -gridSize / 2; z <= gridSize / 2; z += step) {
                       const scope = { x, y, z };
                       try {
                           const dir = new THREE.Vector3(iNode.evaluate(scope), jNode.evaluate(scope), kNode.evaluate(scope));
                           const mag = dir.length();
                           if(mag > maxMagnitude) maxMagnitude = mag;
                           vectors.push({pos: new THREE.Vector3(x,y,z), dir, mag});
                       } catch (err) {
                           errorMessage.textContent = `Error evaluating formula at (${x.toFixed(1)}, ${y.toFixed(1)}, ${z.toFixed(1)})`;
                           return;
                       }
                   }
               }
           }

           const color = new THREE.Color();
           vectors.forEach(({pos, dir, mag}) => {
               const normalizedMag = maxMagnitude > 0 ? mag / maxMagnitude : 0;
               color.setHSL(0.7 - normalizedMag * 0.7, 1.0, 0.5); // From blue to red
               
               const arrow = new THREE.ArrowHelper(dir.clone().normalize(), pos, dir.length() * scale, color.getHex());
               vectorFieldGroup.add(arrow);
           });
           
           scene.add(vectorFieldGroup);
       }

       // --- 3D Animation Loop ---
       function animate3D() {
           requestAnimationFrame(animate3D);
           if (controls) controls.update();
           if (renderer && scene && camera) renderer.render(scene, camera);
       }
       
       // --- Start Application ---
       initialize();

   </script>
</body>
</html>
 

We need your consent to load the translations

We use a third-party service to translate the website content that may collect data about your activity. Please review the details in the privacy policy and accept the service to view the translations.