Skip to content

Instantly share code, notes, and snippets.

@GOROman
Created March 8, 2025 09:25
Show Gist options
  • Save GOROman/5459444fdb13a2056e19440a53b7e7ce to your computer and use it in GitHub Desktop.
Save GOROman/5459444fdb13a2056e19440a53b7e7ce to your computer and use it in GitHub Desktop.
Rotation 3x3 Matrix Visualizer
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;
@GOROman
Copy link
Author

GOROman commented Mar 8, 2025

@GOROman
Copy link
Author

GOROman commented Mar 8, 2025

image

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