# Tactile Labyrinth

A gravity-controlled spherical LED art installation created for Fab Academy 2025. This project features a 99-LED sphere that responds to physical orientation, displaying a "monk pixel" that travels around the sphere on a labyrinthian path.

## Overview

The Tactile Labyrinth is an interactive spherical LED display that simulates a monk traveling through a labyrinth on the surface of a sphere. The monk's movement is controlled by the ball's orientation. It moves faster when at the top of the ball, and slower as it moves down to the equator. At the equator, it tries to turn around and go back up.

## Hardware Requirements

### Core Components
- **Microcontroller**: ESP32 or compatible Arduino board
- **Accelerometer**: Adafruit LSM6DSOX (I2C interface)
- **LEDs**: 99x WS2812B addressable LEDs arranged in a sphere
- **Input**: Push button (active low with pull-up)
- **Status Indicators**: Built-in RGB LED (common cathode)

### Pin Configuration
```cpp
#define LED_PIN     D6    // WS2812B data pin
#define PIN_BUTTON  D10   // Mode switch button
#define PIN_LED_R   17    // Built-in LED red
#define PIN_LED_G   16    // Built-in LED green
#define PIN_LED_B   25    // Built-in LED blue
```

### LED Configuration
- **Count**: 99 LEDs total
- **Type**: WS2812B (RGB, 5V)
- **Color Order**: RGB
- **Brightness**: 128/255 (adjustable)

## Software Architecture

### Dependencies
- `FastLED.h` - LED control library
- `Wire.h` - I2C communication
- `Adafruit_LSM6DSOX.h` - Accelerometer library
- Custom header: `PixelBallLEDMap.h`

### Core Data Structures

#### Coordinate Systems
```cpp
struct CartesianCoord {
    float x, y, z; // Normalized position on unit sphere
};

struct PolarCoord {
    float theta;  // Azimuthal angle (0 to 2π)
    float phi;    // Polar angle (0 to π)
};

struct LEDPosition {
    int ledIndex;
    PolarCoord polarPos;
    CartesianCoord cartesianPos;
    bool isCalibrated;
    bool isSet;
};
```

#### LED Mapping
- `sphereLedPositions[99]` - Pre-calibrated 3D positions for each LED
- `outerPath[99]` - Predefined path sequence for monk movement
- `sphereCardinalLEDs[6]` - Cardinal direction LED indices (+X, -X, +Y, -Y, +Z, -Z)

### Display Modes

The system supports multiple display modes, switchable via button press:

1. **Monk Pixel Mode** - Primary mode featuring the gravity-controlled monk
2. **Debug Report Top Index** - Development mode showing the "up" LED

### Monk Physics Simulation

#### Movement Characteristics
- **Speed Calculation**: Based on dot product between monk position and gravity vector
  - Maximum speed (3.0) at poles (top/bottom)
  - Minimum speed (0.3) at equator
  - Formula: `speed = 0.3 + (2.7 * abs(dot_product))`

#### Direction Control
- Monk reverses direction when reaching equator regions (speed < 0.5)
- Direction persists until next equator encounter
- Small position nudge prevents getting stuck

#### Visual Effects
- **Trailing Effect**: Speed-dependent trail length
  - Longer trail behind monk (1.5x fade radius)
  - Shorter lead ahead (0.5x fade radius)
- **Color Overlay**: Monk appears as white pixels over background

### Background Animation

#### Gravity-Based Coloring
The sphere displays a continuously changing color field based on orientation:

- **Top Third** (dot < -0.333): Solid color at current top hue
- **Bottom Third** (dot > 0.333): Solid color at bottom hue (90° offset)
- **Middle Third**: Smooth interpolation between top and bottom hues
- **Hue Cycling**: Top hue rotates through color wheel every ~12.8 seconds

### Accelerometer Integration

#### Axis Mapping
The system includes configurable axis mapping to handle different physical orientations:

```cpp
AxisMapping ACCEL_X_MAPS_TO = AXIS_NEGATIVE_X;
AxisMapping ACCEL_Y_MAPS_TO = AXIS_NEGATIVE_Y;
AxisMapping ACCEL_Z_MAPS_TO = AXIS_NEGATIVE_Z;
```

#### Sensor Configuration
- **Range**: ±4G accelerometer range
- **Sample Rate**: 104 Hz
- **Gyroscope**: 250 DPS (available but not used)

## Key Algorithms

### Gravity Vector Calculation
```cpp
GravityVector getNormalizedGravity() {
    // Read raw accelerometer data
    // Apply axis mapping
    // Normalize to unit vector
    // Return gravity direction
}
```

### LED Position Lookup
```cpp
int findClosestLED(float x, float y, float z) {
    // Calculate dot product with each LED position
    // Return index of LED with highest dot product
}
```

### Monk Position Update
```cpp
void updateMonkPosition(float speed) {
    // Calculate movement based on speed and direction
    // Handle path wrapping (0-98 LED indices)
    // Apply time-based smooth movement
}
```

## Setup and Calibration

### Hardware Setup
1. Wire LSM6DSOX to I2C pins (SDA/SCL)
2. Connect WS2812B data line to pin D6
3. Wire button between D10 and ground
4. Power LEDs with appropriate 5V supply

### Software Configuration
1. Install required libraries via Arduino Library Manager
2. Add `PixelBallLEDMap.h` to project directory
3. Adjust axis mapping constants if needed
4. Upload code to microcontroller

### LED Position Calibration
The `sphereLedPositions` array contains pre-calibrated 3D coordinates for each LED. For a new sphere configuration:

1. Use accelerometer readings to determine LED positions
2. Update the position array with measured coordinates
3. Verify cardinal directions mapping
4. Test monk path sequence

## Usage

1. **Power On**: System initializes and displays current mode
2. **Button Press**: Cycles through display modes
3. **Physical Interaction**: Tilt/rotate sphere to control monk movement
4. **Serial Monitor**: View debug information and mode changes

## Technical Notes

### Performance Considerations
- Update rate: ~100 Hz (10ms delay)
- Smooth interpolation prevents jerky movement
- Efficient LED mapping minimizes calculation overhead

### Customization Options
- Adjust monk speed parameters
- Modify color schemes and animations
- Add new display modes
- Customize trail effects

## Author

Created by Forrest Oliphant at Aalto Fablab for Fab Academy 2025.
Project URL: https://www.forresto.com/fab-academy/

## License

Copyright © 2025 Forrest Oliphant
