ΠΡ ΡΠΎΠ·Π΄Π°Π΄ΠΈΠΌ ΡΠ΅ΡΡΡΠ΅Ρ ΠΌΠ΅ΡΠ½ΡΠΉ LED-ΠΊΡΠ± (4D-Π³ΠΈΠΏΠ΅ΡΠΊΡΠ±), ΠΊΠΎΡΠΎΡΡΠΉ ΠΏΡΠΎΠ΅ΡΠΈΡΡΠ΅ΡΡΡ Π² 3D-ΠΏΡΠΎΡΡΡΠ°Π½ΡΡΠ²ΠΎ!
4D-Π³ΠΈΠΏΠ΅ΡΠΊΡΠ± (Π’Π΅ΡΡΠ΅ΡΠ°ΠΊΡ):
#include <avr/pgmspace.h>
#define CUBE_SIZE 4
#define LED_COUNT (CUBE_SIZE * CUBE_SIZE * CUBE_SIZE)
#define FRAME_BUFFER_SIZE (LED_COUNT / 8)
// ΠΠΎΠΎΡΠ΄ΠΈΠ½Π°ΡΡ 4D-Π³ΠΈΠΏΠ΅ΡΠΊΡΠ±Π° (16 Π²Π΅ΡΡΠΈΠ½)
const int8_t tesseract[16][4] PROGMEM = {
{-1, -1, -1, -1},
{ 1, -1, -1, -1},
{-1, 1, -1, -1},
{ 1, 1, -1, -1},
{-1, -1, 1, -1},
{ 1, -1, 1, -1},
{-1, 1, 1, -1},
{ 1, 1, 1, -1},
{-1, -1, -1, 1},
{ 1, -1, -1, 1},
{-1, 1, -1, 1},
{ 1, 1, -1, 1},
{-1, -1, 1, 1},
{ 1, -1, 1, 1},
{-1, 1, 1, 1},
{ 1, 1, 1, 1}
};
// Π Π΅Π±ΡΠ° Π³ΠΈΠΏΠ΅ΡΠΊΡΠ±Π° (32 ΡΠ΅Π±ΡΠ°)
const uint8_t edges[32][2] PROGMEM = {
{0,1}, {0,2}, {0,4}, {0,8},
{1,3}, {1,5}, {1,9},
{2,3}, {2,6}, {2,10},
{3,7}, {3,11},
{4,5}, {4,6}, {4,12},
{5,7}, {5,13},
{6,7}, {6,14},
{7,15},
{8,9}, {8,10}, {8,12},
{9,11}, {9,13},
{10,11}, {10,14},
{11,15},
{12,13}, {12,14},
{13,15},
{14,15}
};
// ΠΡΡΠ΅Ρ ΠΊΠ°Π΄ΡΠ°
uint8_t frameBuffer[FRAME_BUFFER_SIZE];
// 4D -> 3D ΠΏΡΠΎΠ΅ΠΊΡΠΈΡ (Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΌΠ°ΡΡΠΈΡΡ Π²ΡΠ°ΡΠ΅Π½ΠΈΡ)
void project4Dto3D(int8_t x, int8_t y, int8_t z, int8_t w,
uint16_t angleXY, uint16_t angleZW,
int8_t* outX, int8_t* outY, int8_t* outZ) {
// ΠΡΠ°ΡΠ΅Π½ΠΈΠ΅ Π² ΠΏΠ»ΠΎΡΠΊΠΎΡΡΠΈ XY
int16_t cosA = pgm_read_word(&cosTable[angleXY]);
int16_t sinA = pgm_read_word(&sinTable[angleXY]);
int16_t rx = (x * cosA - y * sinA) / 256;
int16_t ry = (x * sinA + y * cosA) / 256;
// ΠΡΠ°ΡΠ΅Π½ΠΈΠ΅ Π² ΠΏΠ»ΠΎΡΠΊΠΎΡΡΠΈ ZW
int16_t cosB = pgm_read_word(&cosTable[angleZW]);
int16_t sinB = pgm_read_word(&sinTable[angleZW]);
int16_t rz = (z * cosB - w * sinB) / 256;
int16_t rw = (z * sinB + w * cosB) / 256;
// ΠΠ΅ΡΡΠΏΠ΅ΠΊΡΠΈΠ²Π½Π°Ρ ΠΏΡΠΎΠ΅ΠΊΡΠΈΡ 4D -> 3D
int16_t scale = 64 / (rw + 64 + 1);
*outX = 2 + (rx * scale) / 32;
*outY = 2 + (ry * scale) / 32;
*outZ = 2 + (rz * scale) / 32;
}
// Π Π΅Π½Π΄Π΅ΡΠΈΠ½Π³ Π³ΠΈΠΏΠ΅ΡΠΊΡΠ±Π°
void renderTesseract(uint16_t angleXY, uint16_t angleZW) {
// ΠΡΠΈΡΠ°Π΅ΠΌ Π±ΡΡΠ΅Ρ
memset(frameBuffer, 0, sizeof(frameBuffer));
// Π ΠΈΡΡΠ΅ΠΌ ΡΠ΅Π±ΡΠ°
for (uint8_t i = 0; i < 32; i++) {
uint8_t idx1 = pgm_read_byte(&edges[i][0]);
uint8_t idx2 = pgm_read_byte(&edges[i][1]);
int8_t x1 = pgm_read_byte(&tesseract[idx1][0]);
int8_t y1 = pgm_read_byte(&tesseract[idx1][1]);
int8_t z1 = pgm_read_byte(&tesseract[idx1][2]);
int8_t w1 = pgm_read_byte(&tesseract[idx1][3]);
int8_t x2 = pgm_read_byte(&tesseract[idx2][0]);
int8_t y2 = pgm_read_byte(&tesseract[idx2][1]);
int8_t z2 = pgm_read_byte(&tesseract[idx2][2]);
int8_t w2 = pgm_read_byte(&tesseract[idx2][3]);
int8_t px1, py1, pz1, px2, py2, pz2;
project4Dto3D(x1, y1, z1, w1, angleXY, angleZW, &px1, &py1, &pz1);
project4Dto3D(x2, y2, z2, w2, angleXY, angleZW, &px2, &py2, &pz2);
// ΠΠ»Π³ΠΎΡΠΈΡΠΌ ΠΡΠ΅Π·Π΅Π½Ρ
Π΅ΠΌΠ° Π΄Π»Ρ ΡΠΈΡΠΎΠ²Π°Π½ΠΈΡ Π»ΠΈΠ½ΠΈΠΈ Π² 3D
drawLine3D(px1, py1, pz1, px2, py2, pz2);
}
}
// ΠΠ»Π³ΠΎΡΠΈΡΠΌ ΠΡΠ΅Π·Π΅Π½Ρ
Π΅ΠΌΠ° Π² 3D
void drawLine3D(int8_t x1, int8_t y1, int8_t z1, int8_t x2, int8_t y2, int8_t z2) {
int8_t dx = abs(x2 - x1);
int8_t dy = abs(y2 - y1);
int8_t dz = abs(z2 - z1);
int8_t sx = (x1 < x2) ? 1 : -1;
int8_t sy = (y1 < y2) ? 1 : -1;
int8_t sz = (z1 < z2) ? 1 : -1;
int8_t err = dx - dy - dz;
while (true) {
// Π£ΡΡΠ°Π½Π°Π²Π»ΠΈΠ²Π°Π΅ΠΌ ΠΏΠΈΠΊΡΠ΅Π»Ρ
if (x1 >= 0 && x1 < CUBE_SIZE &&
y1 >= 0 && y1 < CUBE_SIZE &&
z1 >= 0 && z1 < CUBE_SIZE) {
uint16_t idx = (z1 * CUBE_SIZE * CUBE_SIZE + y1 * CUBE_SIZE + x1) / 8;
uint8_t bit = x1 % 8;
frameBuffer[idx] |= (1 << bit);
}
if (x1 == x2 && y1 == y2 && z1 == z2) break;
int8_t e2 = 2 * err;
if (e2 > -dz) {
err -= dy + dz;
x1 += sx;
}
if (e2 < dx) {
err += dx + dy;
z1 += sz;
}
if (e2 > -dx) {
err += dx - dz;
y1 += sy;
}
}
}
// ΠΡΠΏΡΠ°Π²ΠΊΠ° ΠΊΠ°Π΄ΡΠ° Π½Π° LED-ΠΊΡΠ±
void sendFrame() {
for (uint16_t i = 0; i < FRAME_BUFFER_SIZE; i++) {
// ΠΡΠΏΡΠ°Π²Π»ΡΠ΅ΠΌ ΡΠ΅ΡΠ΅Π· SPI Π½Π° MAX7219
SPI.transfer(frameBuffer[i]);
}
}
void setup() {
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV2);
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΠ°ΠΉΠΌΠ΅ΡΠ° Π΄Π»Ρ Π°Π½ΠΈΠΌΠ°ΡΠΈΠΈ
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS10);
OCR1A = 15999; // 1 ΠΊΠΡ
TIMSK1 = (1 << OCIE1A);
sei();
}
volatile uint16_t angleXY = 0;
volatile uint16_t angleZW = 0;
ISR(TIMER1_COMPA_vect) {
angleXY = (angleXY + 1) % 360;
angleZW = (angleZW + 2) % 360;
renderTesseract(angleXY, angleZW);
sendFrame();
}
void loop() {
// ΠΡΠ°ΡΠ΅Π½ΠΈΠ΅ Π³ΠΈΠΏΠ΅ΡΠΊΡΠ±Π° Π² 4D
// ΠΡΠ΅ Π΄Π΅Π»Π°Π΅ΡΡΡ Π² ΠΏΡΠ΅ΡΡΠ²Π°Π½ΠΈΡΡ
delay(1000);
}