Edulicious

Berbagi Berbagai Informasi Pengetahuan

Simulasi Pemantulan Cahaya Pada Cermin Datar

2 min read

Simulasi Cermin Datar Optika Responsif

Simulasi Pemantulan Cermin Datar

100

100


Dibuat menggunakan AI oleh Piki Ahmad

const canvas = document.getElementById(‘simCanvas’);
const ctx = canvas.getContext(‘2d’);

const scaleSlider = document.getElementById(‘scaleSlider’);
const mirrorSlider = document.getElementById(‘mirrorSlider’);
const gridToggleBtn = document.getElementById(‘gridToggleBtn’);
const scaleVal = document.getElementById(‘scaleVal’);
const mirrorVal = document.getElementById(‘mirrorVal’);
const actionBtn = document.getElementById(‘actionBtn’);
const simContainer = document.getElementById(‘simulation-container’);

const originX = 400;
const originY = 250;
const scalePx = 6;

let isGridVisible = true;

let cerminGridX = 0;
let cerminGridY = 0;
let cerminLengthUnit = 40;

const bendaPos = { x: -55, yBottom: 0 };
const mataPos = { x: -40, y: 5 };

let targetBayanganX = 0;
let targetBayanganYTop = 0;

let isDraggingMata = false;
let isDraggingCermin = false;
let isDraggingBenda = false;

let dragOffsetX = 0;
let dragOffsetY = 0;
const interactRadius = 15;

// — MANAJEMEN FULLSCREEN —
actionBtn.addEventListener(‘click’, () => {
if (!document.fullscreenElement) {
if (simContainer.requestFullscreen) { simContainer.requestFullscreen(); }
else if (simContainer.mozRequestFullScreen) { simContainer.mozRequestFullScreen(); }
else if (simContainer.webkitRequestFullscreen) { simContainer.webkitRequestFullscreen(); }
else if (simContainer.msRequestFullscreen) { simContainer.msRequestFullscreen(); }
} else {
if (document.exitFullscreen) { document.exitFullscreen(); }
}
});

document.addEventListener(‘fullscreenchange’, handleFullscreenChange);
document.addEventListener(‘webkitfullscreenchange’, handleFullscreenChange);
document.addEventListener(‘mozfullscreenchange’, handleFullscreenChange);
document.addEventListener(‘MSFullscreenChange’, handleFullscreenChange);

function handleFullscreenChange() {
if (document.fullscreenElement) {
actionBtn.textContent = “Kembali ke Postingan”;
} else {
actionBtn.textContent = “Buka Layar Penuh”;
}
updateSimulation();
}

// — LOGIKA TOGGLE BUTTON —
gridToggleBtn.addEventListener(‘click’, () => {
isGridVisible = !isGridVisible;

if (isGridVisible) {
gridToggleBtn.textContent = “Sembunyikan”;
gridToggleBtn.className = “toggle-grid-btn btn-hide-grid”;
} else {
gridToggleBtn.textContent = “Tampilkan”;
gridToggleBtn.className = “toggle-grid-btn btn-show-grid”;
}
updateSimulation();
});

// — SISTEM RENDERING OPTIK —

function getMirrorBoundsGrid() {
const factor = mirrorSlider.value / 100;
const halfLen = (cerminLengthUnit / 2) * factor;
return { top: cerminGridY + halfLen, bottom: cerminGridY – halfLen };
}

function checkMirrorHit(intersectY_Unit) {
const bounds = getMirrorBoundsGrid();
return (intersectY_Unit = bounds.bottom);
}

function drawGrid() {
const sumbuY_pixelX = originX + (cerminGridX * scalePx);
const sumbuX_pixelY = originY – (cerminGridY * scalePx);

if (isGridVisible) {
ctx.save();

// Grid Background Kecil
ctx.strokeStyle = ‘#e6f0ff’;
ctx.lineWidth = 0.5;
for (let x = 0; x < canvas.width; x += scalePx) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
}
for (let y = 0; y < canvas.height; y += scalePx) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.height, y); ctx.stroke();
}

// Grid Menengah
ctx.strokeStyle = '#ffcccc';
ctx.lineWidth = 1;
for (let x = originX % (scalePx * 10); x < canvas.width; x += scalePx * 10) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
}
for (let y = originY % (scalePx * 10); y < canvas.height; y += scalePx * 10) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
}

// Label Angka Koordinat
ctx.fillStyle = '#555';
ctx.font = '11px sans-serif';

ctx.textAlign = 'center';
const startGridX = Math.floor((0 – sumbuY_pixelX) / scalePx);
const endGridX = Math.ceil((canvas.width – sumbuY_pixelX) / scalePx);
for (let i = Math.ceil(startGridX / 10) * 10; i <= endGridX; i += 10) {
let cx = sumbuY_pixelX + (i * scalePx);
ctx.fillText(Math.abs(i), cx, sumbuX_pixelY + 16);
}

ctx.textAlign = 'left';
const startGridY = Math.floor((sumbuX_pixelY – canvas.height) / scalePx);
const endGridY = Math.ceil((sumbuX_pixelY – 0) / scalePx);
for (let i = Math.ceil(startGridY / 10) * 10; i {
ctx.beginPath(); ctx.moveTo(b.sx, b.sy); ctx.lineTo(b.ex, b.ey); ctx.stroke();
});

ctx.strokeStyle = ‘#222’;
ctx.lineWidth = 1.8;
ctx.fillStyle = ‘#fff’;
ctx.beginPath();
ctx.moveTo(-12, 0);
ctx.bezierCurveTo(-10, -9, 10, -9, 12, 0);
ctx.bezierCurveTo(10, 9, -10, 9, -12, 0);
ctx.closePath();
ctx.fill();
ctx.stroke();

ctx.fillStyle = ‘#5d4037’;
ctx.beginPath(); ctx.arc(2, 0, 5.5, 0, Math.PI * 2); ctx.fill();

ctx.fillStyle = ‘#000’;
ctx.beginPath(); ctx.arc(3.5, 0, 3, 0, Math.PI * 2); ctx.fill();

ctx.restore();
ctx.restore();
}

function drawRayWithArrow(fromX, fromY, toX, toY, color, isDashed = false) {
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
if (isDashed) ctx.setLineDash([5, 5]);
ctx.beginPath(); ctx.moveTo(fromX, fromY); ctx.lineTo(toX, toY); ctx.stroke();
ctx.restore();

const midX = (fromX + toX) / 2;
const midY = (fromY + toY) / 2;
const dx = toX – fromX; const dy = toY – fromY;
const angle = Math.atan2(dy, dx);
const arrowLength = 8;

ctx.save();
ctx.translate(midX, midY); ctx.rotate(angle);
ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(-arrowLength, -arrowLength * 0.5);
ctx.lineTo(-arrowLength * 0.7, 0); ctx.lineTo(-arrowLength, arrowLength * 0.5);
ctx.closePath(); ctx.fill();
ctx.restore();
}

function processAndDrawRays(scaleFactor, bInfo) {
const eyeX = originX + (mataPos.x * scalePx);
const eyeY = originY – (mataPos.y * scalePx);
const mX = originX + (cerminGridX * scalePx);

const jarakBendaKeCermin = cerminGridX – bendaPos.x;
const bayanganGridX = cerminGridX + jarakBendaKeCermin;
const bayX = originX + (bayanganGridX * scalePx);

targetBayanganX = bayX;
targetBayanganYTop = bInfo.bYTop;

const h = 13 * scaleFactor;
const yPositionsGrid = [
bendaPos.yBottom + h,
bendaPos.yBottom,
bendaPos.yBottom + h * 0.65,
bendaPos.yBottom + h * 0.35
];
const rayColors = [‘#009dff’, ‘#e74c3c’, ‘#ff9900’, ‘#00cc44’];

yPositionsGrid.forEach((objYGrid, index) => {
const bayYGrid = objYGrid;
const denom = bayanganGridX – mataPos.x;
if (Math.abs(denom) = bX – 12 && pxX = Math.min(bYTop, bYBot) – 5 && pxY <= Math.max(bYTop, bYBot) + 10);
}

function isOverMata(pxX, pxY) {
const eyePxX = originX + (mataPos.x * scalePx);
const eyePxY = originY – (mataPos.y * scalePx);
return Math.sqrt((pxX – eyePxX)**2 + (pxY – eyePxY)**2) < interactRadius;
}

function isOverCermin(pxX, pxY) {
const cerminPxX = originX + (cerminGridX * scalePx);
const factor = mirrorSlider.value / 100;
const halfLen = (cerminLengthUnit / 2) * factor * scalePx;
const cerminPxYTop = originY – ((cerminGridY + (cerminLengthUnit / 2) * factor) * scalePx);
const cerminPxYBot = originY – ((cerminGridY – (cerminLengthUnit / 2) * factor) * scalePx);
return Math.abs(pxX – cerminPxX) = Math.min(cerminPxYTop, cerminPxYBot) – 5 && pxY <= Math.max(cerminPxYTop, cerminPxYBot) + 5);
}

canvas.addEventListener('mousedown', function(e) {
const coords = getMouseCoords(e);
if (isOverBenda(coords.pixelX, coords.pixelY)) {
isDraggingBenda = true; dragOffsetX = coords.gridX – bendaPos.x; dragOffsetY = coords.gridY – bendaPos.yBottom;
canvas.style.cursor = 'grabbing';
} else if (isOverMata(coords.pixelX, coords.pixelY)) {
isDraggingMata = true; dragOffsetX = coords.gridX – mataPos.x; dragOffsetY = coords.gridY – mataPos.y;
canvas.style.cursor = 'grabbing';
} else if (isOverCermin(coords.pixelX, coords.pixelY)) {
isDraggingCermin = true; dragOffsetX = coords.gridX – cerminGridX; dragOffsetY = coords.gridY – cerminGridY;
canvas.style.cursor = 'grabbing';
}
});

canvas.addEventListener('mousemove', function(e) {
const coords = getMouseCoords(e);
if (!isDraggingMata && !isDraggingCermin && !isDraggingBenda) {
if (isOverBenda(coords.pixelX, coords.pixelY) || isOverMata(coords.pixelX, coords.pixelY) || isOverCermin(coords.pixelX, coords.pixelY)) {
canvas.style.cursor = 'grab';
} else { canvas.style.cursor = 'crosshair'; }
}
if (isDraggingBenda) {
let newX = coords.gridX – dragOffsetX;
bendaPos.x = Math.max(-62, Math.min(newX, cerminGridX – 4));
bendaPos.yBottom = coords.gridY – dragOffsetY; updateSimulation();
} else if (isDraggingMata) {
let newX = coords.gridX – dragOffsetX;
mataPos.x = Math.max(-62, Math.min(newX, cerminGridX – 4));
mataPos.y = coords.gridY – dragOffsetY; updateSimulation();
} else if (isDraggingCermin) {
let newCerminX = coords.gridX – dragOffsetX;
let newCerminY = coords.gridY – dragOffsetY;
const batasKiriTerbesar = Math.max(bendaPos.x, mataPos.x) + 4;
cerminGridX = Math.max(batasKiriTerbesar, Math.min(newCerminX, 60));
cerminGridY = Math.max(-30, Math.min(newCerminY, 30)); updateSimulation();
}
});

window.addEventListener('mouseup', function() {
isDraggingMata = false; isDraggingCermin = false; isDraggingBenda = false;
updateSimulation();
});

scaleSlider.addEventListener('input', updateSimulation);
mirrorSlider.addEventListener('input', updateSimulation);

// Render Inisialisasi Awal
updateSimulation();

About Author

Leave a Reply

Your email address will not be published. Required fields are marked *