Source

inpulse / swarm.c

/**
*
* xswarm style pattern
*
* Copyright (C) 2011 Trammell Hudson <hudson@osresearch.net>
*
* Based on xswarm (c) 1990 by Jeff Butterworth <butterwo@cs.unc.edu>
*
* Permission to use, copy, modify, and/or distribute this software for 
* any purpose with or without fee is hereby granted, provided that the 
* above copyright notice and this permission notice appear in all copies. 
* 
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 
* BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 
* OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
* SOFTWARE.
*
**/

#include "app.h"

#define NUM_BEES 16
#define BEE_TRAIL 12
#define WASP_ACC_MAX	(1 << 4)
#define WASP_VEL_MAX	(3 << 4)

#define BEE_ACC		(1 << 3)
#define BEE_VEL_MAX	(2 << 4)

#define COLOR_GRAY50	((color24_t) { 0x80, 0x80, 0x80, 0 })

struct bee
{
	color24_t color;
	int16_t vx, vy;
	uint16_t x[BEE_TRAIL];
	uint16_t y[BEE_TRAIL];
} __attribute__((packed));;

struct wasp
{
	int16_t vx, vy;
	uint16_t x[1];
	uint16_t y[1];
} __attribute__((packed));

struct swarm_game
{
	uint32_t last_update; // in ms
	struct wasp wasp;
	struct bee bees[NUM_BEES];
} __attribute__((packed));


#define DATA ((struct swarm_game *) app_common_data)
enum { test_size = 1 / (sizeof(*DATA) <= APP_COMMON_SIZE) };


static void
pulse_draw_point(
	uint16_t x,
	uint16_t y,
	color24_t color
)
{
	pulse_set_draw_window(x >> 4, y >> 4, x >> 4, y >> 4);
	pulse_draw_point24(color);
}

static void
pulse_draw_sq(
	uint16_t x,
	uint16_t y,
	uint16_t w,
	color24_t color
)
{
	x >>= 4;
	y >>= 4;

	if (x + w >= SCREEN_WIDTH)
		w = SCREEN_WIDTH - x - 1;
	if (y + w >= SCREEN_HEIGHT)
		w = SCREEN_HEIGHT - y - 1;

	pulse_set_draw_window(x, y, x + w-1, y + w-1);

	unsigned i;
	for (i=0 ; i < w*w ; i++)
		pulse_draw_point24(color);
}




/** Return a small random value between -max and +max, limited
 * to -128 to +127.
 */
static inline int16_t
rand16(
	const int16_t max
)
{
	uint32_t r = rand(); // 32-bit value
	uint16_t rc = r >> 8;
	rc %= 2 * max;
	return rc - max; // shift to -max to +max
}

static inline uint16_t
rand16u(
	const uint16_t max
)
{
	uint32_t r = rand(); // 32-bit value
	uint16_t rc = r >> 8; // grab the middle bits
	return rc % max;
}


static inline int32_t
limit(
	int32_t x,
	int32_t min,
	int32_t max
)
{
	if (x < min)
		return min;
	if (x > max)
		return max;
	return x;
}


/** Make the wasp move erratically up to its maximum acceleration.
 */
static void
wasp_move(
	struct wasp * const wasp
)
{
	// Age the wasp position arrays, erase the oldest
	// and fade the second oldest
	pulse_draw_sq(wasp->x[0], wasp->y[0], 2, COLOR_BLACK24);

	// Accelerate the wasp, up to its maximum limits
	wasp->vx += rand16(WASP_ACC_MAX);
	wasp->vy += rand16(WASP_ACC_MAX);
	wasp->vx = limit(wasp->vx, -WASP_VEL_MAX, WASP_VEL_MAX);
	wasp->vy = limit(wasp->vy, -WASP_VEL_MAX, WASP_VEL_MAX);

	// move the wasp, checking for bounce
	int32_t wx = wasp->x[0] + wasp->vx;
	int32_t wy = wasp->y[0] + wasp->vy;
	if (wx < 0 || wx >= SCREEN_WIDTH << 4)
	{
		wasp->vx = -wasp->vx;
		wx += wasp->vx * 2;
	}
	if (wy < 0 || wy >= SCREEN_HEIGHT << 4)
	{
		wasp->vy = -wasp->vy;
		wy += wasp->vy * 2;
	}

	// Update the points and draw the new wasp position
	wasp->x[0] = wx;
	wasp->y[0] = wy;

	pulse_draw_sq(wasp->x[0], wasp->y[0], 2, COLOR_WHITE24);
}


static void
bee_move(
	const struct wasp * const wasp,
	struct bee * const bee
)
{
	// Age the trails
	pulse_draw_point(
		bee->x[BEE_TRAIL-1],
		bee->y[BEE_TRAIL-1],
		COLOR_BLACK24
	);

	color24_t fade_color = bee->color;
	fade_color.red /= 2;
	fade_color.blue /= 2;
	fade_color.green /= 2;

	pulse_draw_point(bee->x[1], bee->y[1], fade_color);

	unsigned i;
	for (i = BEE_TRAIL-1 ; i >= 1 ; i--)
	{
		bee->x[i] = bee->x[i-1];
		bee->y[i] = bee->y[i-1];
	}

	// Accelerate towards the wasp
	int16_t dx = wasp->x[0] - bee->x[0];
	int16_t dy = wasp->y[0] - bee->y[0];
	int16_t dist = abs(dx) + abs(dy); // approximately
	if (dist == 0)
		dist = 1;

	int16_t ax = (dx * BEE_ACC) / dist;
	int16_t ay = (dy * BEE_ACC) / dist;

	bee->vx = limit(bee->vx + ax, -BEE_VEL_MAX, BEE_VEL_MAX);
	bee->vy = limit(bee->vy + ay, -BEE_VEL_MAX, BEE_VEL_MAX);

	if (rand16u(NUM_BEES) == 0)
	{
		// Don't let things settle down
		bee->vx += rand16(3 << 4);
		bee->vy += rand16(3 << 4);
	}

	int32_t x = bee->x[0] + bee->vx;
	int32_t y = bee->y[0] + bee->vy;


	if (x < 0 || x >= SCREEN_WIDTH<<4)
	{
		bee->vx = -bee->vx;
		x += bee->vx * 2;
	}
	if (y < 0 || y >= SCREEN_HEIGHT<<4)
	{
		bee->vy = -bee->vy;
		y += bee->vy * 2;
	}

	bee->x[0] = x;
	bee->y[0] = y;

	pulse_draw_point(x, y, bee->color);
}


static void
swarm_loop(
	struct pulse_time_tm * now_tm
)
{
	uint32_t now_ms = pulse_get_millis();
	if (now_ms - DATA->last_update < 30)
		return;
	DATA->last_update = now_ms;

	struct wasp * const wasp = &DATA->wasp;
	wasp_move(wasp);
	
	unsigned i;
	for (i=0 ; i < NUM_BEES ; i++)
		bee_move(wasp, &DATA->bees[i]);
}



static void
swarm_init(void)
{
	struct wasp * const wasp = &DATA->wasp;

	pulse_draw_point(wasp->x[2], wasp->y[2], COLOR_BLACK24);
	pulse_draw_point(wasp->x[1], wasp->y[1], COLOR_GRAY50);

	wasp->x[0] = rand16u(SCREEN_WIDTH << 4);
	wasp->y[0] = rand16u(SCREEN_HEIGHT << 4);
	wasp->vx = rand16(WASP_VEL_MAX);
	wasp->vy = rand16(WASP_VEL_MAX);

	unsigned i;
	for (i=0 ; i < NUM_BEES ; i++)
	{
		struct bee * const bee = &DATA->bees[i];
		bee->x[0]	= rand16u(SCREEN_WIDTH << 4);
		bee->y[0]	= rand16u(SCREEN_HEIGHT << 4);
		bee->vx		= rand16(BEE_VEL_MAX);
		bee->vy		= rand16(BEE_VEL_MAX);
		bee->color	= (color24_t) {
			.red		= rand16u(0xFF),
			.green		= rand16u(0xFF),
			.blue		= rand16u(0xFF),
			.alpha		= 0,
		};
	}

	pulse_blank_canvas();
	pulse_oled_set_brightness(100);
}



const app_t swarm_app = {
	.init		= swarm_init,
	.loop		= swarm_loop,
	.button_down	= swarm_init, // reset
};