Difficulty: Intermediate Version: V8
This project introduces 2D rotation math - the foundation for all 3D graphics. You'll control a rotating grid that feels like looking through a window into another dimension.
| Concept | What It Means |
|---|---|
| 2D Rotation | Using sin() and cos() to spin points around a center. |
| Zoom | Changing the grid spacing creates the illusion of moving closer/farther. |
| Split Axis | Horizontal and vertical lines rotate independently - creates wild effects! |
| Combo Reset | UP+DOWN together smoothly returns to default view. |
| Touch | Action |
|---|---|
| LEFT (D0) | Rotate horizontal lines clockwise |
| RIGHT (D3) | Rotate vertical lines counter-clockwise |
| UP (D1) | Zoom In (smaller grid = closer) |
| DOWN (D2) | Zoom Out (larger grid = farther) |
| UP + DOWN | Smooth reset to default view |
/* * Grid Explorer V8.0 (Hold & Combo Reset) * * Controls: * - D0 (Left): Rotate Horizontal CW * - D3 (Right): Rotate Vertical CCW * - D1 (Up): Zoom In * - D2 (Down): Zoom Out * - UP + DOWN: Reset/Return to Default */ #include <Adafruit_GFX.h> #include <Adafruit_SH110X.h> #include <Wire.h> Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1); // Pins #define PIN_LEFT 1 #define PIN_UP 2 #define PIN_DOWN 3 #define PIN_RIGHT 4 // State float displayZoom = 10.0; float angleH = 0.0; // Horizontal lines rotation float angleV = 0.0; // Vertical lines rotation // Targets (for reset) float targetZoom = 10.0; float targetAngle = 0.0; // Config float minZoom = 1.0; float maxZoom = 60.0; float twistSpeed = 0.05; float zoomSpeed = 0.5; struct Point2D { float x, y; }; void setup() { Serial.begin(115200); delay(1000); Wire.begin(5, 6); Wire.setClock(400000); display.begin(0x3C, true); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SH110X_WHITE); display.setCursor(30, 30); display.print("V8.0 LOADED"); display.display(); delay(1000); } // THE ROTATION FUNCTION - This is the magic! Point2D rotate2D(Point2D p, float angle) { float c = cos(angle); float s = sin(angle); return {p.x * c - p.y * s, p.x * s + p.y * c}; } void drawSplitGrid() { float limit = 100.0; Point2D start, end, rStart, rEnd; int cx = 64; // Center X int cy = 32; // Center Y // Vertical lines (using angleV) for (float i = -limit; i <= limit; i += displayZoom) { start = {i, -limit}; end = {i, limit}; rStart = rotate2D(start, angleV); rEnd = rotate2D(end, angleV); display.drawLine(cx + (int)rStart.x, cy + (int)rStart.y, cx + (int)rEnd.x, cy + (int)rEnd.y, SH110X_WHITE); } // Horizontal lines (using angleH) for (float i = -limit; i <= limit; i += displayZoom) { start = {-limit, i}; end = {limit, i}; rStart = rotate2D(start, angleH); rEnd = rotate2D(end, angleH); display.drawLine(cx + (int)rStart.x, cy + (int)rStart.y, cx + (int)rEnd.x, cy + (int)rEnd.y, SH110X_WHITE); } } void loop() { int thresh = 40000; bool tL = touchRead(PIN_LEFT) > thresh; bool tR = touchRead(PIN_RIGHT) > thresh; bool tU = touchRead(PIN_UP) > thresh; bool tD = touchRead(PIN_DOWN) > thresh; // Combo: UP + DOWN = Reset if (tU && tD) { // Smoothly return to defaults float zStep = zoomSpeed * 0.4; if (abs(displayZoom - targetZoom) > zStep) { if (displayZoom < targetZoom) displayZoom += zStep; else displayZoom -= zStep; } else { displayZoom = targetZoom; } float aStep = twistSpeed * 0.4; // Return angles to 0 if (abs(angleH) > aStep) { if (angleH > 0) angleH -= aStep; else angleH += aStep; } else { angleH = 0; } if (abs(angleV) > aStep) { if (angleV > 0) angleV -= aStep; else angleV += aStep; } else { angleV = 0; } } else { // Normal Controls if (tU && !tD) displayZoom += zoomSpeed; if (tD && !tU) displayZoom -= zoomSpeed; if (tL) angleH += twistSpeed; if (tR) angleV -= twistSpeed; // Clamp zoom if (displayZoom < minZoom) displayZoom = minZoom; if (displayZoom > maxZoom) displayZoom = maxZoom; } display.clearDisplay(); drawSplitGrid(); // Status bar display.fillRect(0, 56, 128, 8, SH110X_BLACK); display.drawLine(0, 56, 128, 56, SH110X_WHITE); display.setTextSize(1); display.setTextColor(SH110X_WHITE); if (tU && tD) { display.setCursor(35, 57); display.print("<< RESET >>"); } else { if (tL) { display.setCursor(2, 57); display.print("L"); } if (tU) { display.setCursor(30, 57); display.print("UP"); } if (tD) { display.setCursor(60, 57); display.print("DN"); } if (tR) { display.setCursor(90, 57); display.print("R"); } } display.setCursor(105, 57); display.print("v8"); display.display(); delay(16); }
Every point (x, y) can be rotated around the origin using this formula:
This is the rotation matrix - the foundation of all computer graphics!
| Angle | cos(angle) | sin(angle) | Effect |
|---|---|---|---|
| 0 | 1 | 0 | No change |
| PI/2 (90 deg) | 0 | 1 | 90 deg rotation |
| PI (180 deg) | -1 | 0 | Flip |
Standard grids rotate all lines together. This code rotates horizontal and vertical lines separately:
When they're different, you get impossible-looking geometric patterns!
| Step | Gemini Prompt |
|---|---|
| 1. Draw a grid | "Draw a grid of lines on SH1106 OLED with adjustable spacing" |
| 2. Add zoom | "Make the grid spacing change with UP/DOWN touch buttons" |
| 3. Add rotation | "Explain 2D rotation matrix and show how to rotate points with sin/cos" |
| 4. Apply rotation | "Rotate my grid lines around the screen center before drawing" |
| 5. Split axes | "Use separate rotation angles for horizontal vs vertical lines" |