about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--html/flexagon/TODO.txt89
-rw-r--r--html/flexagon/flexagon.js502
-rw-r--r--html/flexagon/index.html26
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>