diff options
-rw-r--r-- | html/flexagon/TODO.txt | 89 | ||||
-rw-r--r-- | html/flexagon/flexagon.js | 502 | ||||
-rw-r--r-- | html/flexagon/index.html | 26 |
3 files changed, 617 insertions, 0 deletions
diff --git a/html/flexagon/TODO.txt b/html/flexagon/TODO.txt new file mode 100644 index 0000000..b9c9160 --- /dev/null +++ b/html/flexagon/TODO.txt @@ -0,0 +1,89 @@ +HEXAHEXAFLEXAGON IMPLEMENTATION STATUS & TODO +========================================= + +CURRENT IMPLEMENTATION: +--------------------- +- Core mathematical model for a hexahexaflexagon with 19 equilateral triangles +- 3D transformation utilities (rotation matrices, point transformation) +- Animation system with easing functions +- Basic rendering system with debug visualization +- Mouse interaction handling + +WHAT WE'VE TRIED: +--------------- +1. Initial Rendering Debug Steps: + - Added light gray background for visibility + - Drew coordinate axes (red for X, green for Y) + - Made faces semi-transparent (alpha 0.7) + - Added face numbers for identification + - Disabled face culling temporarily + - Added initial rotation transforms (PI/6 for both X and Y) + - Increased scale to 100 for better visibility + +2. State Management: + - Implemented state tracking for faces, transforms, and animation + - Added debug logging for state changes + - Simplified initial state creation + +CURRENT ISSUES: +------------- +1. Visibility: + - Faces are being created but not visible (visible faces count = 0) + - Transform matrix calculations may not be working as expected + - Initial positioning might be incorrect + +2. Triangle Strip: + - Need to verify the initial strip creation geometry + - Connection between triangles needs review + - Pat pattern implementation might need adjustment + +NEXT STEPS TO TRY: +---------------- +1. Geometry Verification: + - Add debug logging for triangle vertices during creation + - Verify triangle dimensions and connections + - Add visual markers for triangle orientation + +2. Transform Pipeline: + - Add step-by-step logging of matrix transformations + - Verify matrix multiplication implementation + - Test transform chain with simpler shapes first + +3. Rendering Improvements: + - Implement proper z-ordering for faces + - Add depth testing + - Improve perspective projection + - Add face normal calculations for proper visibility + +4. Development Tools: + - Add state visualization panel + - Add transform controls for manual positioning + - Add vertex position display + - Create test cases for geometry calculations + +5. Simplification Steps: + - Start with fewer triangles (e.g., 6) to verify basic folding + - Implement single fold before full flexagon + - Add step-by-step folding visualization + +IMMEDIATE NEXT ACTIONS: +-------------------- +1. Add vertex position logging in createTriangle() +2. Verify initial strip layout is correct +3. Test transform pipeline with single triangle +4. Add visual debug helpers for face orientation +5. Implement proper z-depth sorting + +QUESTIONS TO RESOLVE: +------------------ +1. Is the initial triangle strip properly oriented? +2. Are the transformation matrices being applied in the correct order? +3. Is the perspective projection working correctly? +4. Are the face normals being calculated properly? +5. Is the pat pattern being correctly applied to the strip? + +REFERENCES: +---------- +- Original flexagon research pat notation +- 3D graphics pipeline best practices +- Matrix transformation order conventions \ No newline at end of file diff --git a/html/flexagon/flexagon.js b/html/flexagon/flexagon.js new file mode 100644 index 0000000..56069ad --- /dev/null +++ b/html/flexagon/flexagon.js @@ -0,0 +1,502 @@ +// Flexagon Simulation +// This implementation uses a functional programming approach with immutable data structures +// and clear separation between the mathematical model, rendering, and interaction systems. + +// ===== Core Mathematical Model ===== +// The flexagon is modeled as a series of connected triangles in 3D space that can be folded +// A hexahexaflexagon is made from a strip of 19 equilateral triangles + +/** + * Represents a point in 3D space + * @typedef {Object} Point3D + * @property {number} x - X coordinate + * @property {number} y - Y coordinate + * @property {number} z - Z coordinate + */ + +/** + * Represents a 3x3 transformation matrix + * @typedef {Object} Matrix3D + * @property {number[]} values - 3x3 matrix values in row-major order + */ + +/** + * Represents a triangle face of the flexagon + * @typedef {Object} Face + * @property {Point3D[]} vertices - Three vertices of the triangle + * @property {string} color - Color of the face + * @property {number} layer - Layer depth of the face + * @property {number[]} connectedFaces - Indices of connected faces + * @property {number[]} sharedEdges - Indices of shared edges with connected faces + */ + +/** + * Represents the state of the flexagon + * @typedef {Object} FlexagonState + * @property {Face[]} faces - All faces of the flexagon + * @property {number} currentFaceIndex - Index of the currently visible face + * @property {Matrix3D} transform - Current 3D transformation + * @property {boolean} isAnimating - Whether an animation is in progress + * @property {number} animationProgress - Progress of current animation (0-1) + */ + +// Constants for the hexaflexagon +const FLEXAGON_CONFIG = { + triangleCount: 19, // Number of triangles in the strip + colors: ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEEAD', '#D4A5A5'], + animationDuration: 500, // ms + foldAngle: Math.PI / 3, // 60 degrees + sideLength: 100, // Length of triangle sides + // Pat notation for the hexahexaflexagon + // This represents the folding pattern as described in the Flexagon paper + patPattern: [1, 2, 3, 1, 2, 3, 1, 2, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6, 1] +}; + +// ===== 3D Transformation Utilities ===== +/** + * Creates an identity matrix + * @returns {Matrix3D} Identity matrix + */ +const createIdentityMatrix = () => ({ + values: [ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + ] +}); + +/** + * Creates a rotation matrix around the X axis + * @param {number} angle - Rotation angle in radians + * @returns {Matrix3D} Rotation matrix + */ +const createRotationX = (angle) => ({ + values: [ + 1, 0, 0, + 0, Math.cos(angle), -Math.sin(angle), + 0, Math.sin(angle), Math.cos(angle) + ] +}); + +/** + * Creates a rotation matrix around the Y axis + * @param {number} angle - Rotation angle in radians + * @returns {Matrix3D} Rotation matrix + */ +const createRotationY = (angle) => ({ + values: [ + Math.cos(angle), 0, Math.sin(angle), + 0, 1, 0, + -Math.sin(angle), 0, Math.cos(angle) + ] +}); + +/** + * Multiplies two matrices + * @param {Matrix3D} a - First matrix + * @param {Matrix3D} b - Second matrix + * @returns {Matrix3D} Resulting matrix + */ +const multiplyMatrices = (a, b) => { + const result = createIdentityMatrix(); + for (let row = 0; row < 3; row++) { + for (let col = 0; col < 3; col++) { + let sum = 0; + for (let i = 0; i < 3; i++) { + sum += a.values[row * 3 + i] * b.values[i * 3 + col]; + } + result.values[row * 3 + col] = sum; + } + } + return result; +}; + +/** + * Applies a transformation matrix to a point + * @param {Point3D} point - Point to transform + * @param {Matrix3D} matrix - Transformation matrix + * @returns {Point3D} Transformed point + */ +const transformPoint = (point, matrix) => ({ + x: point.x * matrix.values[0] + point.y * matrix.values[1] + point.z * matrix.values[2], + y: point.x * matrix.values[3] + point.y * matrix.values[4] + point.z * matrix.values[5], + z: point.x * matrix.values[6] + point.y * matrix.values[7] + point.z * matrix.values[8] +}); + +// ===== Animation System ===== +/** + * Easing function for smooth animations + * @param {number} t - Time progress (0-1) + * @returns {number} Eased progress + */ +const easeInOutCubic = (t) => { + return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; +}; + +/** + * Creates an equilateral triangle in 3D space + * @param {number} centerX - Center X coordinate + * @param {number} centerY - Center Y coordinate + * @param {number} centerZ - Center Z coordinate + * @param {number} angle - Rotation angle + * @param {number} size - Size of the triangle + * @returns {Point3D[]} Array of three vertices + */ +const createTriangle = (centerX, centerY, centerZ, angle, size) => { + const height = size * Math.sqrt(3) / 2; + return [ + { x: centerX, y: centerY, z: centerZ }, + { + x: centerX + size * Math.cos(angle), + y: centerY + size * Math.sin(angle), + z: centerZ + }, + { + x: centerX + size * Math.cos(angle + Math.PI / 3), + y: centerY + size * Math.sin(angle + Math.PI / 3), + z: centerZ + } + ]; +}; + +/** + * Creates the initial strip of triangles for the hexaflexagon + * @returns {Face[]} Array of connected triangles + */ +const createInitialStrip = () => { + const faces = []; + const size = FLEXAGON_CONFIG.sideLength; + + // Create the strip of triangles following the pat pattern + for (let i = 0; i < FLEXAGON_CONFIG.triangleCount; i++) { + const centerX = (i * size * 1.5); + const centerY = 0; + const centerZ = 0; + const angle = (i % 2) * Math.PI; // Alternate triangle orientations + + faces.push({ + vertices: createTriangle(centerX, centerY, centerZ, angle, size), + color: FLEXAGON_CONFIG.colors[FLEXAGON_CONFIG.patPattern[i] - 1], + layer: Math.floor(i / 6), // Group triangles into layers + connectedFaces: [ + (i + 1) % FLEXAGON_CONFIG.triangleCount, + (i - 1 + FLEXAGON_CONFIG.triangleCount) % FLEXAGON_CONFIG.triangleCount + ], + sharedEdges: [1, 2] // Indices of shared edges with next and previous triangles + }); + } + + return faces; +}; + +/** + * Determines if a face is visible + * @param {Face} face - Face to check + * @param {Matrix3D} transform - Current transformation + * @returns {boolean} Whether the face is visible + */ +const isFaceVisible = (face) => { + // During development, show all faces + return true; +}; + +/** + * Creates the initial state + * @returns {FlexagonState} Initial state of the flexagon + */ +const createInitialState = () => { + const faces = createInitialStrip(); + + // Apply initial transformations to make faces visible + const initialTransform = multiplyMatrices( + createRotationX(Math.PI / 6), // Tilt forward + createRotationY(Math.PI / 6) // Rotate slightly right + ); + + return { + faces: faces, + currentFaceIndex: 0, + transform: initialTransform, + isAnimating: false, + animationProgress: 0 + }; +}; + +/** + * Folds the strip of triangles into a hexagonal shape + * @param {Face[]} faces - Array of faces in the strip + * @returns {Face[]} Folded faces + */ +const foldStrip = (faces) => { + // Implementation of the folding algorithm + // This follows the Tuckerman traverse pattern + const foldedFaces = [...faces]; + const foldAngles = [ + Math.PI / 3, -Math.PI / 3, // First fold + Math.PI / 3, -Math.PI / 3, // Second fold + Math.PI / 3, -Math.PI / 3 // Third fold + ]; + + // Apply the folds + for (let i = 0; i < foldAngles.length; i++) { + const foldIndex = i * 3; + const foldAngle = foldAngles[i]; + + // Transform all vertices after the fold point + for (let j = foldIndex + 1; j < foldedFaces.length; j++) { + foldedFaces[j].vertices = foldedFaces[j].vertices.map(vertex => + transformPoint(vertex, createRotationY(foldAngle)) + ); + } + } + + return foldedFaces; +}; + +/** + * Performs a flex operation on the flexagon + * @param {FlexagonState} state - Current state + * @returns {FlexagonState} New state after flexing + */ +const flex = (state) => { + if (state.isAnimating) return state; + + const currentFace = state.faces[state.currentFaceIndex]; + const nextFaceIndex = currentFace.connectedFaces[0]; + + // Create rotation matrices for the animation + const startRotation = createIdentityMatrix(); + const endRotation = multiplyMatrices( + createRotationX(FLEXAGON_CONFIG.foldAngle), + createRotationY(Math.PI / 3) + ); + + // Apply an initial rotation to better show the 3D structure + const initialRotation = createRotationX(Math.PI / 6); + const combinedRotation = multiplyMatrices(endRotation, initialRotation); + + return { + ...state, + currentFaceIndex: nextFaceIndex, + isAnimating: true, + animationProgress: 0, + transform: startRotation + }; +}; + +/** + * Updates the animation state + * @param {FlexagonState} state - Current state + * @param {number} deltaTime - Time since last update in ms + * @returns {FlexagonState} Updated state + */ +const updateAnimation = (state, deltaTime) => { + if (!state.isAnimating) return state; + + const newProgress = Math.min(1, state.animationProgress + deltaTime / FLEXAGON_CONFIG.animationDuration); + const easedProgress = easeInOutCubic(newProgress); + + // Interpolate between start and end transformations + const startRotation = createIdentityMatrix(); + const endRotation = multiplyMatrices( + createRotationX(FLEXAGON_CONFIG.foldAngle), + createRotationY(Math.PI / 3) + ); + + const interpolatedMatrix = { + values: startRotation.values.map((value, i) => + value + (endRotation.values[i] - value) * easedProgress + ) + }; + + return { + ...state, + animationProgress: newProgress, + transform: interpolatedMatrix, + isAnimating: newProgress < 1 + }; +}; + +// ===== Rendering System ===== +/** + * Projects a 3D point onto 2D canvas coordinates with perspective + * @param {Point3D} point - 3D point to project + * @param {number} canvasWidth - Width of the canvas + * @param {number} canvasHeight - Height of the canvas + * @returns {Object} 2D coordinates {x, y} + */ +const projectPoint = (point, canvasWidth, canvasHeight) => { + // Perspective projection with a fixed focal length + const focalLength = 500; + const scale = focalLength / (focalLength + point.z); + + return { + x: point.x * scale + canvasWidth / 2, + y: point.y * scale + canvasHeight / 2 + }; +}; + +/** + * Calculates the normal vector of a face + * @param {Face} face - Face to calculate normal for + * @returns {Point3D} Normal vector + */ +const calculateFaceNormal = (face) => { + const v1 = face.vertices[1]; + const v2 = face.vertices[2]; + + return { + x: (v1.y - v2.y) * (v1.z - v2.z), + y: (v1.z - v2.z) * (v1.x - v2.x), + z: (v1.x - v2.x) * (v1.y - v2.y) + }; +}; + +/** + * Renders the flexagon on the canvas + * @param {CanvasRenderingContext2D} ctx - Canvas context + * @param {FlexagonState} state - Current state + */ +const renderFlexagon = (ctx, state) => { + const canvas = ctx.canvas; + + // Clear canvas with a light gray background for debugging + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Debug: Draw coordinate axes + ctx.beginPath(); + ctx.strokeStyle = 'red'; + ctx.moveTo(canvas.width/2, canvas.height/2); + ctx.lineTo(canvas.width/2 + 50, canvas.height/2); + ctx.stroke(); + ctx.beginPath(); + ctx.strokeStyle = 'green'; + ctx.moveTo(canvas.width/2, canvas.height/2); + ctx.lineTo(canvas.width/2, canvas.height/2 - 50); + ctx.stroke(); + + // Center the flexagon + const centerX = canvas.width / 2; + const centerY = canvas.height / 2; + const scale = 100; // Increase scale to make faces more visible + + // Draw all faces for debugging + state.faces.forEach((face, index) => { + // Transform vertices + const projectedPoints = face.vertices.map(vertex => { + const transformed = transformPoint(vertex, state.transform); + return { + x: centerX + transformed.x * scale, + y: centerY + transformed.y * scale + }; + }); + + // Draw face + ctx.beginPath(); + ctx.moveTo(projectedPoints[0].x, projectedPoints[0].y); + projectedPoints.slice(1).forEach(point => { + ctx.lineTo(point.x, point.y); + }); + ctx.closePath(); + + // Fill with semi-transparent color + ctx.fillStyle = face.color; + ctx.globalAlpha = 0.7; + ctx.fill(); + + // Draw edges + ctx.strokeStyle = '#000'; + ctx.globalAlpha = 1.0; + ctx.stroke(); + + // Draw face number + ctx.fillStyle = '#000'; + ctx.font = '14px Arial'; + const centerPoint = { + x: projectedPoints.reduce((sum, p) => sum + p.x, 0) / 3, + y: projectedPoints.reduce((sum, p) => sum + p.y, 0) / 3 + }; + ctx.fillText(index.toString(), centerPoint.x, centerPoint.y); + }); + + // Reset alpha + ctx.globalAlpha = 1.0; +}; + +// ===== Interaction System ===== +/** + * Sets up mouse interaction for the flexagon + * @param {HTMLCanvasElement} canvas - Canvas element + * @param {Function} onFlex - Callback when flexing occurs + */ +const setupInteraction = (canvas, onFlex) => { + let isDragging = false; + let startX = 0; + + canvas.addEventListener('mousedown', (e) => { + isDragging = true; + startX = e.clientX; + }); + + canvas.addEventListener('mousemove', (e) => { + if (!isDragging) return; + + const deltaX = e.clientX - startX; + if (Math.abs(deltaX) > 50) { // Threshold for flexing + onFlex(); + isDragging = false; + } + }); + + canvas.addEventListener('mouseup', () => { + isDragging = false; + }); + + canvas.addEventListener('mouseleave', () => { + isDragging = false; + }); +}; + +// ===== Main Application ===== +const main = () => { + const canvas = document.getElementById('flexagonCanvas'); + const ctx = canvas.getContext('2d'); + + // Set canvas size + canvas.width = 600; + canvas.height = 600; + + console.log('Canvas initialized:', canvas.width, 'x', canvas.height); + + // Initialize state + let state = createInitialState(); + console.log('Initial state created:', { + faceCount: state.faces.length, + vertices: state.faces[0]?.vertices + }); + + let lastTime = performance.now(); + + // Animation loop + const animate = (currentTime) => { + const deltaTime = currentTime - lastTime; + lastTime = currentTime; + + state = updateAnimation(state, deltaTime); + renderFlexagon(ctx, state); + + requestAnimationFrame(animate); + }; + + // Setup interaction + setupInteraction(canvas, () => { + state = flex(state); + }); + + // Start animation loop + requestAnimationFrame(animate); +}; + +// Start the application when the DOM is loaded +document.addEventListener('DOMContentLoaded', main); diff --git a/html/flexagon/index.html b/html/flexagon/index.html new file mode 100644 index 0000000..5d42dec --- /dev/null +++ b/html/flexagon/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Flexagon Simulation</title> + <style> + body { + margin: 0; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background: #f0f0f0; + } + canvas { + border: 1px solid #ccc; + background: white; + } + </style> +</head> +<body> + <canvas id="flexagonCanvas"></canvas> + <script src="flexagon.js"></script> +</body> +</html> |