Created
March 8, 2025 09:25
-
-
Save GOROman/5459444fdb13a2056e19440a53b7e7ce to your computer and use it in GitHub Desktop.
Rotation 3x3 Matrix Visualizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useRef, useState } from 'react'; | |
import * as THREE from 'three'; | |
const ThreeDCoordinateSystem = () => { | |
const mountRef = useRef(null); | |
const [rotationMatrix, setRotationMatrix] = useState([ | |
[1, 0, 0], | |
[0, 1, 0], | |
[0, 0, 1] | |
]); | |
const [angleY, setAngleY] = useState(0); | |
useEffect(() => { | |
// Scene setup | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0xffffff); | |
// Camera setup | |
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
camera.position.set(2, 2, 5); | |
camera.lookAt(0, 0, 0); | |
// Renderer setup | |
const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
mountRef.current.appendChild(renderer.domElement); | |
// Create origin point | |
const originGeometry = new THREE.SphereGeometry(0.1, 32, 32); | |
const originMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); | |
const origin = new THREE.Mesh(originGeometry, originMaterial); | |
scene.add(origin); | |
// Create coordinate axes | |
const axisLength = 3; | |
// X-axis (red) | |
const xAxisGeometry = new THREE.BufferGeometry(); | |
const xAxisPoints = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(axisLength, 0, 0)]; | |
xAxisGeometry.setFromPoints(xAxisPoints); | |
const xAxisMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 }); | |
const xAxis = new THREE.Line(xAxisGeometry, xAxisMaterial); | |
scene.add(xAxis); | |
// X-axis arrow | |
const xArrowGeometry = new THREE.ConeGeometry(0.1, 0.3, 32); | |
const xArrowMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); | |
const xArrow = new THREE.Mesh(xArrowGeometry, xArrowMaterial); | |
xArrow.position.set(axisLength, 0, 0); | |
xArrow.rotation.z = -Math.PI / 2; | |
scene.add(xArrow); | |
// Y-axis (green) | |
const yAxisGeometry = new THREE.BufferGeometry(); | |
const yAxisPoints = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, axisLength, 0)]; | |
yAxisGeometry.setFromPoints(yAxisPoints); | |
const yAxisMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 }); | |
const yAxis = new THREE.Line(yAxisGeometry, yAxisMaterial); | |
scene.add(yAxis); | |
// Y-axis arrow | |
const yArrowGeometry = new THREE.ConeGeometry(0.1, 0.3, 32); | |
const yArrowMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); | |
const yArrow = new THREE.Mesh(yArrowGeometry, yArrowMaterial); | |
yArrow.position.set(0, axisLength, 0); | |
scene.add(yArrow); | |
// Z-axis (blue) | |
const zAxisGeometry = new THREE.BufferGeometry(); | |
const zAxisPoints = [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, axisLength)]; | |
zAxisGeometry.setFromPoints(zAxisPoints); | |
const zAxisMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff }); | |
const zAxis = new THREE.Line(zAxisGeometry, zAxisMaterial); | |
scene.add(zAxis); | |
// Z-axis arrow | |
const zArrowGeometry = new THREE.ConeGeometry(0.1, 0.3, 32); | |
const zArrowMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff }); | |
const zArrow = new THREE.Mesh(zArrowGeometry, zArrowMaterial); | |
zArrow.position.set(0, 0, axisLength); | |
zArrow.rotation.x = Math.PI / 2; | |
scene.add(zArrow); | |
// Add grids to the XZ and XY planes - with light color and not part of the rotating scene | |
const gridSize = 6; | |
const gridDivisions = 6; | |
// XZ plane grid (horizontal) | |
const xzGridHelper = new THREE.GridHelper(gridSize, gridDivisions, 0xcccccc, 0xeeeeee); | |
// Rotate grid to XZ plane (default is XY) | |
xzGridHelper.rotation.x = Math.PI / 2; | |
// XY plane grid (vertical) | |
const xyGridHelper = new THREE.GridHelper(gridSize, gridDivisions, 0xcccccc, 0xeeeeee); | |
// No rotation needed for XY plane as it's the default | |
// Create a separate scene for the non-rotating elements | |
const staticScene = new THREE.Scene(); | |
staticScene.add(xzGridHelper); | |
staticScene.add(xyGridHelper); | |
// Create a blurred circular path in the XZ plane - radius matches axis length | |
const circleRadius = axisLength; // Set radius to match axis length | |
const circleSegments = 128; | |
const circleGeometry = new THREE.BufferGeometry(); | |
const circlePoints = []; | |
for (let i = 0; i <= circleSegments; i++) { | |
const theta = (i / circleSegments) * Math.PI * 2; | |
circlePoints.push(new THREE.Vector3( | |
circleRadius * Math.cos(theta), | |
0, | |
circleRadius * Math.sin(theta) | |
)); | |
} | |
circleGeometry.setFromPoints(circlePoints); | |
// Create material with glow effect | |
const circleMaterial = new THREE.LineBasicMaterial({ | |
color: 0xff6600, | |
transparent: true, | |
opacity: 0.5, | |
linewidth: 2 | |
}); | |
const circleLine = new THREE.Line(circleGeometry, circleMaterial); | |
staticScene.add(circleLine); | |
// Add multiple blurred circles for glow effect | |
const blurLayers = 5; | |
for (let i = 0; i < blurLayers; i++) { | |
const opacity = 0.3 - (i * 0.05); | |
const blurMaterial = new THREE.LineBasicMaterial({ | |
color: 0xff6600, | |
transparent: true, | |
opacity: opacity, | |
linewidth: 1 | |
}); | |
const blurCircle = new THREE.Line(circleGeometry, blurMaterial); | |
blurCircle.scale.set(1 + (i * 0.03), 1, 1 + (i * 0.03)); | |
staticScene.add(blurCircle); | |
} | |
// Add axis labels | |
const addLabel = (text, position, color) => { | |
const canvas = document.createElement('canvas'); | |
canvas.width = 128; | |
canvas.height = 64; | |
const context = canvas.getContext('2d'); | |
context.fillStyle = '#ffffff'; | |
context.fillRect(0, 0, canvas.width, canvas.height); | |
context.font = '48px Arial'; | |
context.fillStyle = color; | |
context.textAlign = 'center'; | |
context.textBaseline = 'middle'; | |
context.fillText(text, canvas.width / 2, canvas.height / 2); | |
const texture = new THREE.CanvasTexture(canvas); | |
const material = new THREE.SpriteMaterial({ map: texture }); | |
const sprite = new THREE.Sprite(material); | |
sprite.position.copy(position); | |
sprite.scale.set(0.5, 0.25, 1); | |
scene.add(sprite); | |
}; | |
addLabel('X', new THREE.Vector3(axisLength + 0.3, 0, 0), '#ff0000'); | |
addLabel('Y', new THREE.Vector3(0, axisLength + 0.3, 0), '#00ff00'); | |
addLabel('Z', new THREE.Vector3(0, 0, axisLength + 0.3), '#0000ff'); | |
// Animation loop | |
let rotate = true; | |
let time = 0; | |
const animate = () => { | |
requestAnimationFrame(animate); | |
if (rotate) { | |
time += 0.01; | |
// Only rotate around Y-axis as requested | |
const newAngleY = time * 0.5; | |
setAngleY(newAngleY); | |
// Create rotation matrix for Y-axis only | |
const cosY = Math.cos(newAngleY); | |
const sinY = Math.sin(newAngleY); | |
// Y-axis rotation matrix (keeps Y-axis as 0,1,0) | |
// Transposed from typical presentation - rows are now X, Y, Z axes | |
const rotY = [ | |
[cosY, 0, sinY], | |
[0, 1, 0], | |
[-sinY, 0, cosY] | |
]; | |
// Apply rotation to scene | |
scene.rotation.x = 0; | |
scene.rotation.y = newAngleY; | |
scene.rotation.z = 0; | |
// Set the rotation matrix (Y-axis rotation only) | |
setRotationMatrix(rotY); | |
} | |
renderer.render(scene, camera); | |
renderer.autoClear = false; | |
renderer.render(staticScene, camera); | |
renderer.autoClear = true; | |
}; | |
animate(); | |
// Add controls for rotation | |
const handleKeyDown = (e) => { | |
if (e.key === ' ') { | |
rotate = !rotate; | |
} | |
}; | |
window.addEventListener('keydown', handleKeyDown); | |
// Handle window resize | |
const handleResize = () => { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
}; | |
window.addEventListener('resize', handleResize); | |
// Cleanup | |
return () => { | |
window.removeEventListener('keydown', handleKeyDown); | |
window.removeEventListener('resize', handleResize); | |
mountRef.current.removeChild(renderer.domElement); | |
}; | |
}, []); | |
// Convert angle to degrees and π representation | |
const angleYDegrees = (angleY * 180 / Math.PI) % 360; | |
// Create pi representation (e.g., 0.5π, 1π, 1.5π) | |
const getPiNotation = (radians) => { | |
// Normalize to 0-2π range | |
const normalized = radians % (2 * Math.PI); | |
const inPiUnits = normalized / Math.PI; | |
// Format based on value | |
if (Math.abs(inPiUnits) < 0.01) return "0"; | |
if (Math.abs(inPiUnits - 1) < 0.01) return "π"; | |
if (Math.abs(inPiUnits - 2) < 0.01) return "2π"; | |
if (Math.abs(inPiUnits - 0.5) < 0.01) return "π/2"; | |
if (Math.abs(inPiUnits - 1.5) < 0.01) return "3π/2"; | |
// For other values, format as Nπ | |
const formatted = Math.round(inPiUnits * 100) / 100; | |
return `${formatted}π`; | |
}; | |
// Format angle display | |
const formatAngle = (angle) => { | |
return Math.round(angle * 100) / 100; | |
}; | |
// Get calculated values for display | |
const cosY = Math.cos(angleY); | |
const sinY = Math.sin(angleY); | |
const roundedCosY = Math.round(cosY * 100) / 100; | |
const roundedSinY = Math.round(sinY * 100) / 100; | |
return ( | |
<div className="w-full h-screen"> | |
<div ref={mountRef} className="w-full h-full" /> | |
{/* Rotation Matrix Display */} | |
<div className="absolute top-4 right-4 bg-white p-4 rounded shadow"> | |
<h3 className="font-bold mb-2 text-center">Y-Axis Rotation Matrix</h3> | |
{/* Angle Display */} | |
<div className="mb-3 text-center"> | |
<div>θ = {getPiNotation(angleY)} = {formatAngle(angleYDegrees)}°</div> | |
</div> | |
{/* Matrix with cosine/sine notation - Mathematical style */} | |
<div className="border-2 border-gray-300 p-4 rounded mb-3 flex items-center justify-center"> | |
<div className="text-3xl mr-2">(</div> | |
<div> | |
<div className="flex justify-center"> | |
<div className="w-16 flex items-center justify-center m-1 text-red-600 font-mono">cos(θ)</div> | |
<div className="w-16 flex items-center justify-center m-1 text-red-600 font-mono">0</div> | |
<div className="w-16 flex items-center justify-center m-1 text-red-600 font-mono">sin(θ)</div> | |
</div> | |
<div className="flex justify-center"> | |
<div className="w-16 flex items-center justify-center m-1 text-green-600 font-mono">0</div> | |
<div className="w-16 flex items-center justify-center m-1 text-green-600 font-mono">1</div> | |
<div className="w-16 flex items-center justify-center m-1 text-green-600 font-mono">0</div> | |
</div> | |
<div className="flex justify-center"> | |
<div className="w-16 flex items-center justify-center m-1 text-blue-600 font-mono">-sin(θ)</div> | |
<div className="w-16 flex items-center justify-center m-1 text-blue-600 font-mono">0</div> | |
<div className="w-16 flex items-center justify-center m-1 text-blue-600 font-mono">cos(θ)</div> | |
</div> | |
</div> | |
<div className="text-3xl ml-2">)</div> | |
</div> | |
{/* Matrix with calculated values - Mathematical style */} | |
<h4 className="text-center mb-1">Current Values:</h4> | |
<div className="border-2 border-gray-300 p-4 rounded flex items-center justify-center"> | |
<div className="text-3xl mr-2">(</div> | |
<div> | |
<div className="flex justify-center"> | |
<div className="w-16 flex items-center justify-center m-1 text-red-600 font-mono">{roundedCosY}</div> | |
<div className="w-16 flex items-center justify-center m-1 text-red-600 font-mono">0</div> | |
<div className="w-16 flex items-center justify-center m-1 text-red-600 font-mono">{roundedSinY}</div> | |
</div> | |
<div className="flex justify-center"> | |
<div className="w-16 flex items-center justify-center m-1 text-green-600 font-mono">0</div> | |
<div className="w-16 flex items-center justify-center m-1 text-green-600 font-mono">1</div> | |
<div className="w-16 flex items-center justify-center m-1 text-green-600 font-mono">0</div> | |
</div> | |
<div className="flex justify-center"> | |
<div className="w-16 flex items-center justify-center m-1 text-blue-600 font-mono">-{Math.abs(roundedSinY)}</div> | |
<div className="w-16 flex items-center justify-center m-1 text-blue-600 font-mono">0</div> | |
<div className="w-16 flex items-center justify-center m-1 text-blue-600 font-mono">{roundedCosY}</div> | |
</div> | |
</div> | |
<div className="text-3xl ml-2">)</div> | |
</div> | |
<div className="mt-2 text-sm"> | |
<div className="flex items-center"> | |
<div className="w-4 h-4 bg-red-600 mr-2"></div> | |
<span>X axis (row 1)</span> | |
</div> | |
<div className="flex items-center"> | |
<div className="w-4 h-4 bg-green-600 mr-2"></div> | |
<span>Y axis (row 2)</span> | |
</div> | |
<div className="flex items-center"> | |
<div className="w-4 h-4 bg-blue-600 mr-2"></div> | |
<span>Z axis (row 3)</span> | |
</div> | |
</div> | |
</div> | |
<div className="absolute bottom-4 left-4 bg-white p-2 rounded shadow"> | |
<p className="text-sm">Press spacebar to toggle rotation</p> | |
</div> | |
</div> | |
); | |
}; | |
export default ThreeDCoordinateSystem; |
Author
GOROman
commented
Mar 8, 2025

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment