i found something rather intresting, one can achieve hardware-accelerated 3D graphics on the SSD1306.
Here’s how it works:
- First, we need to set the screen to mux=1. Then, we activate the accelerator by accessing register 0xD2, 4 (5?,0xC?). it wil also work without the register, if unsupported, but then vram needs to be redrawn this is doable on i2c but not as good as the accelerator, and should be easier on spi, but i have not yet tested it.
how to draw triangles directly using the 128x2 image created when mux=1.
I shift this image along the y-axis for each triangle. For example, if a triangle has coordinates (10, 5), (20, 12), and (20, 30), the line will smoothly move across the screen from y=5 to y=30.
To achieve this, I use a conventional triangle drawing technique that usually involves calling a function called “horizontal_line (x_start_of_the_line, y, length_of_line, color)”. Typically, when drawing to a buffer, you can simply use memset to draw the line into the buffer.
However, in this case, I’m not drawing to a buffer; I’m drawing directly onto the display. So, instead, I utilize the “fill rectangle” command to write the horizontal line into row 0. this can often be accomplish this with just one 0x24 draw rectangle command (2 at max). Then i move my 128x2 image to the correct line on the screen, fill the row with next horizontal line, and move the image one pixel down.
see attached code.
(please note that this triangle drawer is a quick hack and has lots of bugs when it comes to clipping, it only works within the screen coordinates…it needs fixing).
By continuously drawing these triangles without stopping, the OLED is showing 3D graphics directly!
void vectoscopeHline(s16 x1, s16 y1, s16 len, u8 c)
{
if (y1 < 0)return;
if (y1 > 63)return;
if (x1 > 127)return;
if (x1 < 0)len += x1, x1 = 0;
if (len < 0)return;
if (x1 + len > 127)
{
len -= x1 + len - 127;
}
static int last_xstart;
static int last_xend;
static int last_y;
static u8 row = 0;
// 0x24,direction,row_start,clear_pixels,row_end,x_start,x_end,
const u8 cmd1[] = {
0x24, 0,
row,
0/* clear*/,
row,
0, 127,
// 0xe3,0xe3,0xe3,0xe3 , // clear line
};
const u8 cmd1b[] = {
0x24, 0, row, 1/*set*/, row, x1, x1 + len,
};// set line
const u8 cmd2[] = {
0xd3, y1 & 63,
// 0x40//+62
// , 0xe3,0xe3 // move line
};
if ((last_xstart != x1) || (last_xend != x1 + len))
{
if ((last_xstart != x1) && (last_xend != x1 + len))
{
if ((x1 > last_xstart) || (x1 + len < last_xend)) // the new line to be drawn needs to be cleared, if the white area has not expanded
for (u8 i = 0; i < sizeof(cmd1); i++)
{
os_i2c_write_byte_fast(cmd1[i]);//+(y1&63));
i2c_counter++;
}
for (u8 i = 0; i < sizeof(cmd1b); i++)
{
os_i2c_write_byte_fast(cmd1b[i]);//+(y1&63));
i2c_counter++;
}
}
else // one is identical
{
if (last_xstart == x1) // change at the right side
{
if (x1 + len < last_xend) // contract
{
const u8 contract_cmd[] = {
0x24, 0,
row,
0/* clear*/,
row,
x1 + len + 1, 127,
// 0xe3,0xe3,0xe3,0xe3 , // clear line
};
for (u8 i = 0; i < sizeof(contract_cmd); i++)
{
os_i2c_write_byte_fast(contract_cmd[i]);//+(y1&63));
i2c_counter++;
}
}
else
{
for (u8 i = 0; i < sizeof(cmd1b); i++)
{
os_i2c_write_byte_fast(cmd1b[i]);//+(y1&63));
i2c_counter++;
}
}
}
else
{
if (x1 > last_xstart) // contract
{
const u8 contract_cmd[] = {
0x24, 0,
row,
0/* clear*/,
row,
0, x1 - 1,
// 0xe3,0xe3,0xe3,0xe3 , // clear line
};
for (u8 i = 0; i < sizeof(contract_cmd); i++)
{
os_i2c_write_byte_fast(contract_cmd[i]);//+(y1&63));
i2c_counter++;
}
}
else //expand, just draw it
{
for (u8 i = 0; i < sizeof(cmd1b); i++)
{
os_i2c_write_byte_fast(cmd1b[i]);//+(y1&63));
i2c_counter++;
}
}
}
}
last_xstart = x1;
last_xend = x1 + len;
}
if (last_y != y1)
{
for (u8 i = 0; i < sizeof(cmd2); i++)
{
os_i2c_write_byte_fast(cmd2[i]);//+(y1&63));
i2c_counter++;
}
last_y = y1;
}
i2c_counter += 2;
}
void VectoscopeTriangle(s16 x1, s16 y1, s16 x2, s16 y2, s16 x3, s16 y3, u8 c)
{
cli();
if (x2 < 0)return;
if (x2 > 126)return;
if (y2 < 0)return;
if (y2 > 62)return;
if (x1 < 0)return;
if (x1 > 126)return;
if (y1 < 0)return;
if (y1 > 62)return;
if (x1 < 0)return;
if (x1 > 126)return;
if (y1 < 0)return;
if (y1 > 62)return;
os_i2c_start();
os_i2c_write_byte(SSD1306_ADDRESS);
os_i2c_write_byte(0x0); // command mode
os_i2c_write_byte(0x40); // command mode
s16 t1x, t2x, y, minx, maxx, t1xp, t2xp;
u8 change = 0;
//const u8 terminate_line = _cur_seg + 7;
//note: x and y are swapped to get better organization of the line
int i;
//for(i=20;i<40;i++)vectoscopeHline(30,i,20,0);
//return;
minx = y1;
if (y2 < minx)minx = y2;
if (y3 < minx)minx = y3;
maxx = y1;
if (y2 > maxx)maxx = y2;
if (y3 > maxx)maxx = y3;
s16 signx1, signx2, dx1, dy1, dx2, dy2;
s16 e1, e2;
// Sort vertices
if (y1 > y2)
{
swapu8(y1, y2);
swapu8(x1, x2);
}
if (y1 > y3)
{
swapu8(y1, y3);
swapu8(x1, x3);
}
if (y2 > y3)
{
swapu8(y2, y3);
swapu8(x2, x3);
}
t1x = x1;
t2x = x1;
y = y1; // Starting points
dx1 = (s8)(x2 - x1);
if (dx1 < 0)
{
dx1 = -dx1;
signx1 = -1;
}
else
signx1 = 1;
dy1 = (s8)(y2 - y1);
dx2 = (s8)(x3 - x1);
if (dx2 < 0)
{
dx2 = -dx2;
signx2 = -1;
}
else signx2 = 1;
dy2 = (s8)(y3 - y1);
if (dy1 > dx1)
{ // swap values
swapu8(dx1, dy1);
change |= 1; // = true;
}
if (dy2 > dx2)
{ // swap values
swapu8(dy2, dx2);
change |= 2; // = true;
}
e2 = (u8)(dx2 >> 1);
// Flat top, just process the second half
if (y1 == y2) goto next;
e1 = (u8)(dx1 >> 1);
for (u8 i = 0; i < dx1;)
{
t1xp = 0; t2xp = 0;
if (t1x < t2x)
{
minx = t1x;
maxx = t2x;
}
else
{
minx = t2x;
maxx = t1x;
}
// process first line until y value is about to change
while (i < dx1)
{
i++;
e1 += dy1;
while (e1 >= dx1)
{
e1 -= dx1;
if (change & 1)
t1xp = signx1;
else
goto next1;
}
if (change & 1) break;
t1x += signx1;
}
// Move line
next1:
// process second line until y value is about to change
while (1)
{
e2 += dy2;
while (e2 >= dx2) {
e2 -= dx2;
if (change & 2)
t2xp = signx2;
else
goto next2;
}
if (change & 2)
break;
t2x += signx2;
}
next2:
if (minx > t1x) minx = t1x;
if (minx > t2x) minx = t2x;
if (maxx < t1x) maxx = t1x;
if (maxx < t2x) maxx = t2x;
//*/
vectoscopeHline(minx, y, maxx - minx, c);
// x_vline(minx, y, maxx, c, linebuffer); // Draw line from min to max points found on the y
// if (y == terminate_line)return;
// Now increase y
if (!(change & 1)) t1x += signx1;
t1x += t1xp;
if (!(change & 2)) t2x += signx2;
t2x += t2xp;
y += 1;
if (y == y2) break;
}
next:
// Second half
dx1 = (s8)(x3 - x2);
if (dx1 < 0)
{
dx1 = -dx1;
signx1 = -1;
}
else
signx1 = 1;
dy1 = (s8)(y3 - y2);
t1x = x2;
if (dy1 > dx1)
{ // swap values
swapu8(dy1, dx1);
change |= 1; // = true;
}
else
change &= 2; //false;
e1 = (u8)(dx1 >> 1);
for (u8 i = 0; i <= dx1; i++)
{
t1xp = 0; t2xp = 0;
if (t1x < t2x)
{
minx = t1x;
maxx = t2x;
}
else
{
minx = t2x;
maxx = t1x;
}
// process first line until y value is about to change
while (i < dx1) {
e1 += dy1;
while (e1 >= dx1)
{
e1 -= dx1;
if (change & 1)
{
t1xp = signx1;
break;
}
else
goto next3;
}
if (change & 1)
break;
t1x += signx1;
if (i < dx1) i++;
}
next3:
// process second line until y value is about to change
while (t2x != x3)
{
e2 += dy2;
while (e2 >= dx2)
{
e2 -= dx2;
if (change & 2)
t2xp = signx2;
else
goto next4;
}
if (change & 2)
break;
t2x += signx2;
}
next4:
if (minx > t1x) minx = t1x;
if (minx > t2x) minx = t2x;
if (maxx < t1x) maxx = t1x;
if (maxx < t2x) maxx = t2x;
// */
vectoscopeHline(minx, y, maxx - minx, c);
// x_vline(minx, y, maxx, c, linebuffer); // Draw line from min to max points found on the y
// if (y == terminate_line)return;
// Now increase y
if (!(change & 1)) t1x += signx1;
t1x += t1xp;
if (!(change & 2)) t2x += signx2;
t2x += t2xp;
y += 1;
if (y > y3) return;
}
}
--------------------------------- thecube:
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define NUM_OF_INDICES 12
#define SCREEN_CENTER_X (SCREEN_WIDTH / 2)
#define SCREEN_CENTER_Y (SCREEN_HEIGHT / 2)
#define OBJ_SCALE (float)2500
#define CAMERA_DISTANCE 15
float objX = 0;
float objY = 0;
float objZ = 10;
float rotationX = .2;
float rotationY = .3;
const float vertices[] = {
-1.0f,-1.0f,-1.0f,
-1.0f,-1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f,-1.0f,
-1.0f,-1.0f,-1.0f,
-1.0f, 1.0f,-1.0f,
1.0f,-1.0f, 1.0f,
-1.0f,-1.0f,-1.0f,
1.0f,-1.0f,-1.0f,
1.0f, 1.0f,-1.0f,
1.0f,-1.0f,-1.0f,
-1.0f,-1.0f,-1.0f,
-1.0f,-1.0f,-1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f,-1.0f,
1.0f,-1.0f, 1.0f,
-1.0f,-1.0f, 1.0f,
-1.0f,-1.0f,-1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f,-1.0f, 1.0f,
1.0f,-1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f,-1.0f,-1.0f,
1.0f, 1.0f,-1.0f,
1.0f,-1.0f,-1.0f,
1.0f, 1.0f, 1.0f,
1.0f,-1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f,-1.0f,
-1.0f, 1.0f,-1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f,-1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f,-1.0f, 1.0f
};
static void rotate(float x, float y, float angle,float s,float c, float *r1, float *r2)
{
*r1 = x * c - y * s;
*r2 = y * c + x * s;
}
static const u8 tri_color[12]={// 0,0,0,0,0,0,
50, 63, 40, 63, 50, 40,
40, 50, 50, 63, 63, 40,
};
typedef struct triangle
{
u8 x1,y1,x2,y2,x3,y3,c,z;
}triangle;
void Calc3D() {
// GfxApiBeginTriangles();
const float srx= sin(rotationX);
const float crx=cos(rotationX);
const float sry= sin(rotationY);
const float cry= cos(rotationY);
float z_sort=0;
u8 cnt=3,cnt2=0;
int x[3],y[3];
for (int i = 0; i < 3*12; i++)
{
u8 a = i*3;
float x1, y1, z1;
x1 = vertices[a + 0];
y1 = vertices[a + 1];
z1 = vertices[a + 2];
rotate(x1, z1, rotationY,sry,cry,&x1,&z1);
rotate(y1, z1, rotationX,srx,crx,&y1,&z1);
x1 = (float)(x1 * OBJ_SCALE);
y1 = (float)(y1 * OBJ_SCALE);
x1 += objX;
y1 += objY;
z1 += objZ;
float inverse=1.0/(z1 * CAMERA_DISTANCE);
x1 *=inverse;
y1 *=inverse;
x1 += SCREEN_CENTER_X;
y1 += SCREEN_CENTER_Y;
z_sort+=z1*7;
// GfxApiStoreTrianglePoint(x1, y1);// , x2, y2);
cnt--;
x[cnt]=x1;
y[cnt]=y1;
if(!cnt)
{
// Perform hidden surface removal by checking triangle orientation
float dx1 = x[1] - x[0];
float dy1 = y[1] - y[0];
float dx2 = x[2] - x[0];
float dy2 = y[2] - y[0];
float cross_product = dx1 * dy2 - dy1 * dx2;
if (cross_product > 0) {
// Call the triangle function only for visible triangles
VectoscopeTriangle(x[0],y[0],x[1],y[1],x[2],y[2],0);
// yield();
}
cnt=3;
os_i2c_stop();
cnt2++;
z_sort=0;
}
}
rotationX += .051;
//if(rotationX>2*M_PI)rotationX-=2*M_PI;
rotationY += .05;
//if(rotationY>2*M_PI)rotationX-=2*M_PI;
}