diff --git a/app/index.html b/app/index.html index 80cd168..9fa606b 100644 --- a/app/index.html +++ b/app/index.html @@ -4,21 +4,41 @@ EFSim + +
-
- - + + +
+ + +
-
- - -
-
diff --git a/app/script/Field3D.js b/app/script/Field3D.js new file mode 100644 index 0000000..67762e0 --- /dev/null +++ b/app/script/Field3D.js @@ -0,0 +1,185 @@ +import * as THREE from "three"; +import PointCharge from "./PointCharge.js"; + +// 指定座標における電場ベクトルを計算 +export const PosToElectricFieldVector = (pos, point_charges) => { + var electric_field_vector = new THREE.Vector3(0, 0, 0); // 合成ベクトル + for (const point_charge of point_charges) { + const diff = new THREE.Vector3(0, 0, 0); + diff.subVectors(pos, point_charge.pos); // 点電荷と観測点との差分ベクトル + + const r_sq = diff.lengthSq(); // 点電荷と観測点との距離^2 (平方根処理をなくすため) + const k = 8.987552 * 10 ** 9; // クーロン定数 + + const factor = r_sq ? (k * point_charge.charge) / r_sq ** 2 : 0; // ベクトルにかける係数 + + diff.multiplyScalar(factor); + + electric_field_vector.add(diff); + } + return electric_field_vector; +}; + +/// 電気力線 +class ElectricLines3D extends THREE.Group { + constructor(point_charges) { + super(); + this.point_charges = point_charges; + this.createField(point_charges); + } + + /// @param origin 始点 (PointCharge) + createElectricLineGeometry = ( + origin_charge, + vector, + point_charges, + length + ) => { + const points = [origin_charge.pos.clone()]; + const _origin = origin_charge.pos.clone(); + _origin.add(vector); + for (var i = 0; i < length; i++) { + const d_vector = PosToElectricFieldVector(_origin, point_charges); + d_vector.normalize(); + if (origin_charge.charge < 0) d_vector.multiplyScalar(-1); + // 点電荷との衝突判定 + for (const point_charge of point_charges) { + const diff = new THREE.Vector3(0, 0, 0); + diff.subVectors(_origin, point_charge.pos); + if (diff.length() < 1) { + return new THREE.BufferGeometry().setFromPoints(points); + } + } + _origin.add(d_vector); + points.push(_origin.clone()); + } + return new THREE.BufferGeometry().setFromPoints(points); + }; + + createField(point_charges) { + const line_material = new THREE.LineBasicMaterial({ color: 0xaaaaff }); + for (const point_charge of point_charges) { + for (var n_theta = 0; n_theta < 5; n_theta++) { + for (var n_phi = 0; n_phi < 5; n_phi++) { + const theta = ((Math.PI * 2) / 5) * n_theta; + const phi = ((Math.PI * 2) / 5) * n_phi; + const x = 5 * Math.sin(theta) * Math.cos(phi); + const y = 5 * Math.sin(theta) * Math.sin(phi); + const z = 5 * Math.cos(theta); + + const line = new THREE.Line( + this.createElectricLineGeometry( + point_charge, + new THREE.Vector3(x, y, z), + point_charges, + 1000 + ), + line_material + ); + this.add(line); + } + } + } + } +} + +// 点電荷 +class PointCharges3D extends THREE.Group { + constructor(point_charges) { + super(); + const geometry = new THREE.SphereGeometry(1, 32, 32); + const material_minus = new THREE.MeshBasicMaterial({ color: 0x0000ff }); + const material_plus = new THREE.MeshBasicMaterial({ color: 0xff0000 }); + for (const point_charge of point_charges) { + const material = point_charge.charge > 0 ? material_plus : material_minus; + const sphere = new THREE.Mesh(geometry, material); + sphere.position.copy(point_charge.pos); + this.add(sphere); + } + } +} + +// 電界ベクトル +class ElectricFieldVectors3D extends THREE.Group { + constructor(point_charges) { + super(); + this.point_charges = point_charges; + + for (var point_charge of point_charges) { + for (var x = -4; x <= 4; x++) { + for (var y = -4; y <= 4; y++) { + for (var z = -4; z <= 4; z++) { + if (x ** 2 + y ** 2 + z ** 2 > 5 ** 2) continue; + if (x === 0 && y === 0 && z === 0) continue; + const origin = new THREE.Vector3(x * 10, y * 10, z * 10); + origin.add(point_charge.pos); + const electric_field_vector = PosToElectricFieldVector( + origin, + point_charges + ); + + const len = electric_field_vector.length(); + this.add( + new THREE.ArrowHelper( + electric_field_vector.normalize(), + origin, + 1, + 0xffffff, + 2, + 0.5 + ) + ); + } + } + } + } + } +} + +/// 電界 +export class Field3D extends THREE.Group { + constructor(point_charges) { + super(); + this.point_charges = point_charges; + this.enablePointCharges(true); + } + + point_charges_3d = null; + electric_lines_3d = null; + electric_field_vectors_3d = null; + + /// 点電荷の表示切替 + enablePointCharges = (enable) => { + if (enable) { + if (this.point_charges_3d == null) + this.point_charges_3d = new PointCharges3D(this.point_charges); + this.add(this.point_charges_3d); + } else { + this.remove(this.point_charges_3d); + } + }; + + /// 電気力線の表示切替 + enableElectricLines = (enable) => { + if (enable) { + if (this.electric_lines_3d == null) + this.electric_lines_3d = new ElectricLines3D(this.point_charges); + this.add(this.electric_lines_3d); + } else { + this.remove(this.electric_lines_3d); + } + }; + + /// 電界ベクトルの表示切替 + enableElectricFieldVectors = (enable) => { + if (enable) { + if (this.electric_field_vectors_3d == null) + this.electric_field_vectors_3d = new ElectricFieldVectors3D( + this.point_charges + ); + this.add(this.electric_field_vectors_3d); + } else { + this.remove(this.electric_field_vectors_3d); + } + }; +} diff --git a/app/script/Init.js b/app/script/Init.js index a7e412c..e2a3bb4 100644 --- a/app/script/Init.js +++ b/app/script/Init.js @@ -1,5 +1,6 @@ import * as THREE from "three"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +import { TransformControls } from "three/addons/controls/TransformControls"; // シーンを作成 export const CreateScene = () => { @@ -45,13 +46,26 @@ export const CreateCamera = () => { }; // マウスコントロールを作成 -export const CreateControls = (camera, renderer) => { - const controls = new OrbitControls(camera, renderer.domElement); +export const CreateControls = (camera, dom) => { + const controls = new OrbitControls(camera, dom); controls.autoRotate = true; // 自動回転 controls.autoRotateSpeed = 1; // 自動回転の速度 controls.enableDamping = true; // 視点の移動を滑らかにする - controls.dampingFactor = 0.5; // 滑らか度合い + controls.dampingFactor = 0.2; // 滑らか度合い return controls; }; + +export const CreateTransformControls = (camera, dom, controls, scene) => { + const transControls = new TransformControls(camera, dom); + + transControls.addEventListener("dragging-changed", (event) => { + controls.enablePan = !event.value; + controls.enableRotate = !event.value; + }); + + scene.add(transControls); + + return transControls; +}; \ No newline at end of file diff --git a/app/script/main.js b/app/script/main.js index 0918b4d..3e2ff76 100644 --- a/app/script/main.js +++ b/app/script/main.js @@ -1,212 +1,80 @@ import * as THREE from "three"; -import { TransformControls } from "three/addons/controls/TransformControls"; +import * as EFSim from "./Init.js"; import PointCharge from "./PointCharge.js"; +import { Field3D } from "./Field3D.js"; -import { - CreateScene, - CreateRenderer, - CreateCamera, - CreateControls, -} from "./Init.js"; +const scene = EFSim.CreateScene(); +const renderer = EFSim.CreateRenderer(document.getElementById("canvas")); +const camera = EFSim.CreateCamera(); -const scene = CreateScene(); -const renderer = CreateRenderer(document.getElementById("canvas")); -const camera = CreateCamera(); -const controls = CreateControls(camera, renderer); +// マウスコントロール +const controls = EFSim.CreateControls(camera, renderer.domElement); -// 自動回転の有無チェックイベント -document - .getElementById("checkbox_auto_rotate") - .addEventListener("change", (e) => { - controls.autoRotate = e.target.checked; - }); - -// 指定座標における電場ベクトルを計算 -const PosToElectricFieldVector = (pos, point_charges) => { - var electric_field_vector = new THREE.Vector3(0, 0, 0); // 合成ベクトル - for (const point_charge of point_charges) { - const diff = new THREE.Vector3(0, 0, 0); - diff.subVectors(pos, point_charge.pos); // 点電荷と観測点との差分ベクトル - - const r_sq = diff.lengthSq(); // 点電荷と観測点との距離^2 (平方根処理をなくすため) - const k = 8.987552 * 10 ** 9; // クーロン定数 - - const factor = r_sq ? (k * point_charge.charge) / r_sq ** 2 : 0; // ベクトルにかける係数 - - diff.multiplyScalar(factor); - - electric_field_vector.add(diff); - } - return electric_field_vector; -}; - -// オブジェクトコントローラー -const transControls = new TransformControls(camera, renderer.domElement); -// scene.add(transControls); +// ドラッグでオブジェクトを移動するためのコントロール +const transControls = EFSim.CreateTransformControls( + camera, + renderer.domElement, + controls, + scene +); // 点電荷たち const point_charges = [ - new PointCharge(-0.00001, new THREE.Vector3(60, 0, 60)), - new PointCharge(0.0001, new THREE.Vector3(20, 60, 0)), + new PointCharge(0.00001, new THREE.Vector3(60, 0, 60)), + new PointCharge(-0.0001, new THREE.Vector3(20, 60, 0)), new PointCharge(+0.00001, new THREE.Vector3(-50, 0, 20)), new PointCharge(0.00005, new THREE.Vector3(20, 0, 0)), ]; -// 球 -const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); -const sphereMaterialRed = new THREE.MeshBasicMaterial( - { color: 0xdd5555, wireframe: false }, - new THREE.MeshStandardMaterial() -); -const sphereMaterialBlue = new THREE.MeshBasicMaterial( - { color: 0x5555dd, wireframe: false }, - new THREE.MeshStandardMaterial() -); -for (const charge of point_charges) { - const mesh = new THREE.Mesh( - sphereGeometry, - charge.charge > 0 ? sphereMaterialRed : sphereMaterialBlue - ); - mesh.position.copy(charge.pos); - scene.add(mesh); - // transControls.attach(mesh); -} - -/// @param origin 始点 (PointCharge) -const CreateElectricLineGeometry = (origin_charge, vector, point_charges, length) => { - const points = [origin_charge.pos.clone()]; - const _origin = origin_charge.pos.clone(); - _origin.add(vector); - for (var i = 0; i < length; i++) { - const d_vector = PosToElectricFieldVector(_origin, point_charges); - d_vector.normalize(); - if (origin_charge.charge < 0) d_vector.multiplyScalar(-1); - // 点電荷との衝突判定 - for (const point_charge of point_charges) { - const diff = new THREE.Vector3(0, 0, 0); - diff.subVectors(_origin, point_charge.pos); - if (diff.length() < 1) { - console.log("hit"); - return new THREE.BufferGeometry().setFromPoints(points); - } - } - _origin.add(d_vector); - points.push(_origin.clone()); - } - return new THREE.BufferGeometry().setFromPoints(points); -}; - -const line_material = new THREE.LineBasicMaterial({ color: 0x0000ff }); -for (const point_charge of point_charges) { - - for (var n_theta = 0; n_theta < 10; n_theta++) { - for (var n_phi = 0; n_phi < 10; n_phi++) { - const theta = (Math.PI * 2) / 10 * n_theta; - const phi = (Math.PI * 2) / 10 * n_phi; - const x = 5 * Math.sin(theta) * Math.cos(phi); - const y = 5 * Math.sin(theta) * Math.sin(phi); - const z = 5 * Math.cos(theta); +const field_3d = new Field3D(point_charges); - const line = new THREE.Line( - CreateElectricLineGeometry( - point_charge, - new THREE.Vector3(x, y, z), - point_charges, - 500 - ), - line_material - ); - scene.add(line); - } - } +// 自動回転切り替え +{ + const dom_switch = document.getElementById("checkbox_auto_rotate"); + controls.autoRotate = dom_switch.checked; // 初期値 + dom_switch.addEventListener("change", (e) => { + controls.autoRotate = e.target.checked; + }); } -const CreateElectricFieldVector = () => { - var arrows = []; - for (var point_charge of point_charges) { - for (var x = -5; x <= 5; x++) { - for (var y = -5; y <= 5; y++) { - for (var z = -5; z <= 5; z++) { - if (x ** 2 + y ** 2 + z ** 2 > 5 ** 2) continue; - if (x === 0 && y === 0 && z === 0) continue; - const origin = new THREE.Vector3(x * 5, y * 5, z * 5); - origin.add(point_charge.pos); - const electric_field_vector = PosToElectricFieldVector( - origin, - point_charges - ); - - const len = electric_field_vector.length(); - arrows.push( - new THREE.ArrowHelper( - electric_field_vector.normalize(), - origin, - 1, - 0xffffff, - 2, - 0.5 - ) - ); - } - } - } - } - return arrows; -}; - -var arrows = CreateElectricFieldVector(); - -document - .getElementById("checkbox_electric_field_vectors") - .addEventListener("change", (e) => { - if (e.target.checked) { - for (const arrow of arrows) { - scene.add(arrow); - } +// 2D/3D切り替え +{ + const dom_switch = document.getElementById("dimension_toggle_switch"); + const dom_slider = document.getElementById("dimension_toggle_slider"); + const ChangeDimension = (is_3d) => { + if (is_3d) { + dom_slider.textContent = "3D"; + scene.add(field_3d); + // transControls.attach(field_3d); } else { - for (const arrow of arrows) { - scene.remove(arrow); - } + dom_slider.textContent = "2D"; + scene.remove(field_3d); + // transControls.detach(field_3d); } + }; + ChangeDimension(dom_switch.checked); // 初期値 + dom_switch.addEventListener("change", (e) => { + ChangeDimension(e.target.checked); }); +} -transControls.addEventListener("mouseDown", () => { - // オブジェクト操作時、OrbitControls無効化 - controls.enablePan = false; - controls.enableRotate = false; -}); - -transControls.addEventListener("mouseUp", () => { - // オブジェクト操作解除時、OrbitControls有効化 - controls.enablePan = true; - controls.enableRotate = true; -}); - -transControls.addEventListener("change", () => { - // オブジェクト操作解除時、OrbitControls有効化 - if (document.getElementById("checkbox_electric_field_vectors").checked) { - // for (const arrow of arrows) { - // scene.remove(arrow); - // } - // arrows = CreateElectricFieldVector(); - // for (const arrow of arrows) { - // scene.add(arrow); - // } - } -}); - -// 座表軸 -// scene.add(new THREE.AxesHelper()); +// 電気力線 表示/非表示 +{ + const checkbox = document.getElementById("checkbox_electric_lines"); + field_3d.enableElectricLines(checkbox.checked); // 初期値 + checkbox.addEventListener("change", (e) => { + field_3d.enableElectricLines(e.target.checked); + }); +} -// 床 -const meshFloor = new THREE.Mesh( - new THREE.BoxGeometry(100, 0.0001, 100), - new THREE.MeshBasicMaterial( - { color: 0x555555 }, - new THREE.MeshStandardMaterial() - ) -); -// scene.add(meshFloor); +// 電界ベクトル 表示/非表示 +{ + const checkbox = document.getElementById("checkbox_electric_field_vectors"); + field_3d.enableElectricFieldVectors(checkbox.checked); // 初期値 + checkbox.addEventListener("change", (e) => { + field_3d.enableElectricFieldVectors(e.target.checked); + }); +} // エントリーポイント const main = () => { diff --git a/app/static/github.png b/app/static/github.png deleted file mode 100644 index 9490ffc..0000000 Binary files a/app/static/github.png and /dev/null differ diff --git a/app/static/kagaku_genshi.png b/app/static/kagaku_genshi.png new file mode 100644 index 0000000..ce74e52 Binary files /dev/null and b/app/static/kagaku_genshi.png differ diff --git a/app/style/style.css b/app/style/style.css index 8f66685..a052238 100644 --- a/app/style/style.css +++ b/app/style/style.css @@ -13,8 +13,7 @@ height: 100%; } -.enable_auto_rotate, -.enable_electric_field_vectors { +.checkbox { display: flex; margin: 10px; padding: 10px; @@ -25,4 +24,4 @@ input[type="checkbox"] { margin: 0 10px 0 0; -} +} \ No newline at end of file diff --git a/app/style/toggle.css b/app/style/toggle.css new file mode 100644 index 0000000..3384c7f --- /dev/null +++ b/app/style/toggle.css @@ -0,0 +1,37 @@ +/* トグルボタン */ + +.dimension_toggle { + display: flex; + align-items: center; + position: relative; + width: auto; + height: 40px; + margin: 20px; + border-radius: 10px; + border: 2px solid #515862; + cursor: pointer; + overflow: hidden; + user-select: none; +} + +.dimension_toggle input { + display: none; +} + +#dimension_toggle_slider { + position: absolute; + width: 50%; + height: 100%; + border-radius: 8px 0 0 8px; + background-color: #64696f; + transition: 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + color: #fff; +} + +.dimension_toggle:has(:checked) #dimension_toggle_slider { + border-radius: 0 8px 8px 0; + transform: translateX(100%); +}