Source

watches / vector / vector.c

Full commit
/** \file
 * Draw a rotating clock where the second hand is a digital readout.
 */
#include <string.h>
#include "main.h"
#include "draw.h"
#include "sin_table.h"

/** Rotation matrix for draw_digit */
typedef struct
{
	uint8_t rot_angle;
	int8_t rot_cos;
	int8_t rot_sin;
} rotation_t;

static rotation_t last_rot;


void
init(void)
{
	last_rot.rot_angle = 0;
	last_rot.rot_sin = 0;
	last_rot.rot_cos = 127;
	
}


void
button_down(void)
{
	// Just reset when they hit the button
	init();
}


void
button_up(
	uint32_t ms
)
{
	// Nothing to do
	(void) ms;
}


/** The hands are drawn under the digits */
static void
draw_hand(
	uint32_t deg,
	uint8_t len,
	color24_t color
)
{
	uint8_t d = (deg * 256) / 360; // scale to 2 PI
	int8_t sx = sin_lookup(d);
	int8_t sy = cos_lookup(d);

	const uint32_t cx = VSCREEN_WIDTH/2;
	const uint32_t cy = VSCREEN_HEIGHT/2;
	const uint32_t ex = cx + (sx * len) / 128;
	const uint32_t ey = cy - (sy * len) / 128;

	draw_line(color, cx, cy, ex, ey);

	fill(color, ex - 1, ey - 1, 3, 3);
}


/** Vector font.
 * Pack the 2bit coordinates into for a line into a single 8-bit uint8_t.
 */
#define LINE(x1,y1,x2,y2) (x1 << 6 | y1 << 4 | x2 <<2 | y2 << 0)

typedef struct
{
	uint8_t lines[8];
} char_t;


static const char_t digits[] =
{
	[0] = { .lines = {
		LINE(0,0, 0,1),
		LINE(0,1, 2,1),
		LINE(2,1, 2,0),
		LINE(2,0, 0,0),
	} },

	[1] = { .lines = {
		LINE(0,1, 2,1),
	} },

	[2] = { .lines = {
		LINE(0,1, 0,0),
		LINE(0,0, 1,1),
		LINE(1,1, 2,1),
		LINE(2,1, 2,0),
	} },

	[3] = { .lines = {
		LINE(0,0, 0,1),
		LINE(0,1, 2,1),
		LINE(2,1, 2,0),
		LINE(1,0, 1,1),
	} },

	[4] = { .lines = {
		LINE(2,0, 1,0),
		LINE(1,0, 1,1),
		LINE(2,1, 0,1),
	} },

	[5] = { .lines = {
		LINE(0,0, 0,1),
		LINE(0,1, 1,1),
		LINE(1,1, 1,0),
		LINE(1,0, 2,0),
		LINE(2,0, 2,1),
	} },

	[6] = { .lines = {
		LINE(2,1, 2,0),
		LINE(2,0, 0,0),
		LINE(0,0, 0,1),
		LINE(0,1, 1,1),
		LINE(1,1, 1,0),
	} },

	[7] = { .lines = {
		LINE(2,0, 2,1),
		LINE(2,1, 0,0),
	} },

	[8] = { .lines = {
		LINE(0,0, 0,1),
		LINE(0,1, 2,1),
		LINE(2,1, 2,0),
		LINE(2,0, 0,0),
		LINE(1,0, 1,1),
	} },

	[9] = { .lines = {
		LINE(1,1, 1,0),
		LINE(1,0, 2,0),
		LINE(2,0, 2,1),
		LINE(2,1, 0,1),
	} },

	[10] = { .lines = {
		LINE(1,0, 1,1),
	} },
};



static void
draw_digit(
	color24_t color,
	const rotation_t * const rot,
	int32_t scale,
	char c,
	const int32_t x0,
	const int32_t y0
)
{
	if (c == ':')
		c = 10;
	else
	if ('0' <= c && c <= '9')
		c -= '0';
	else
		return;

	const char_t * const d = &digits[(uint8_t) c];

	for (const uint8_t * line = d->lines ; *line ; line++)
	{
		const uint8_t l = *line;
		int32_t x1 = x0 + scale * ((l >> 6) & 0x3);
		int32_t y1 = y0 + scale * ((l >> 4) & 0x3);
		int32_t x2 = x0 + scale * ((l >> 2) & 0x3);
		int32_t y2 = y0 + scale * ((l >> 0) & 0x3);

		// Rotate the coordinates by the matrix and into screen frame
		int32_t py1 = (x1 * rot->rot_sin + y1 * rot->rot_cos) / 128
			+ VSCREEN_HEIGHT / 2;
		int32_t px1 = (x1 * rot->rot_cos - y1 * rot->rot_sin) / 128
			+ VSCREEN_WIDTH / 2;
		int32_t py2 = (x2 * rot->rot_sin + y2 * rot->rot_cos) / 128
			+ VSCREEN_HEIGHT / 2;
		int32_t px2 = (x2 * rot->rot_cos - y2 * rot->rot_sin) / 128
			+ VSCREEN_WIDTH / 2;

		if (px1 < 0 || px1 >= VSCREEN_WIDTH
		||  px2 < 0 || px2 >= VSCREEN_WIDTH
		||  py1 < 0 || py1 >= VSCREEN_HEIGHT
		||  py2 < 0 || py2 >= VSCREEN_HEIGHT)
			continue;

		//printf("%d/%d: %d %d %d %d\n", rot->rot_sin, rot->rot_cos, px1, py1, px2, py2);
		draw_line(color, px1, py1, px2, py2);
	}
}



static uint32_t last_update;
static char last_msg[16];
static uint32_t last_min;
static uint32_t last_hour;

void
draw(
	uint32_t cur_ms
)
{
	uint32_t ms = cur_ms % 1000; cur_ms /= 1000;
	uint32_t sec = cur_ms % 60; cur_ms /= 60;
	uint8_t min = cur_ms % 60; cur_ms /= 60;
	uint8_t hour = cur_ms % 24; // cur_ms %= 24;

	uint8_t rot_angle = 128 + (256 * (sec * 6 + (ms * 6) / 1000)) / 360;
	if (rot_angle == last_rot.rot_angle)
		return;

	char msg[16];
	sprintf(msg, "%02d%02d%02d",
		hour,
		min,
		sec
	);


	rotation_t new_rot = {
		.rot_angle	= rot_angle,
		.rot_sin	= sin_lookup(rot_angle),
		.rot_cos	= cos_lookup(rot_angle),
	};

	int32_t scale = 13;
	int32_t y = -2 * scale;

	// Draw the hands
	color24_t hand_color = { 40, 8, 10 };
	draw_hand(last_min, VSCREEN_WIDTH/2, COLOR_BLACK);
	last_min = min * 6 + sec / 10;
	draw_hand(last_min, VSCREEN_WIDTH/2, hand_color);

	draw_hand(last_hour, VSCREEN_WIDTH/3, COLOR_BLACK);
	last_hour = (hour % 12) * 30 + min / 2;
	draw_hand(last_hour, VSCREEN_WIDTH/3, hand_color);

	fill(hand_color, VSCREEN_WIDTH/2 - 2, VSCREEN_HEIGHT/2 - 2, 5, 5);
	

	// Draw the digits
	color24_t color = COLOR_GREEN;

	for (int i = 0 ; i < 6 ; i++)
	{
		char c;

		// Erase the old one if it is not a leading zero
		c = last_msg[i];
		if (i != 0 || c != '0')
			draw_digit(
				COLOR_BLACK,
				&last_rot,
				scale,
				c,
				-scale,
				y
			);

		// Draw the new one if it is not a leading zero
		c = msg[i];
		if (i != 0 || c != '0')
			draw_digit(
				color,
				&new_rot,
				scale,
				c,
				-scale,
				y
			);

		// Move to the right
		y += scale + 4;
		color.green = (color.green * 2ul) / 3;

		// Scale down a bit after each division
		if ((i & 1) == 1)
			scale = (scale * 4) / 5;
		else
			scale = (scale * 7) / 8;
	}


	last_rot = new_rot;
	memcpy(last_msg, msg, sizeof(msg));

	last_update = cur_ms;
}