Mirai's Miscellaneous Misadventures

M19 / core / appearances.c

// copyright 2022 zamfofex
// license: AGPLv3 or later

#include <mimimi/models.h>
#include <mimimi/appearances.h>
#include <mimimi/allocators.h>

#include "appearances.h"

#include "math.c"

static unsigned char mimimi_rotating_image_pixel(struct mimimi_rotating_image *rotating_image, unsigned char angle, int x, int y)
{
	y += rotating_image->oy;
	
	int h = rotating_image->count;
	if (y < 0) return 0;
	if (y >= h) return 0;
	
	int w = rotating_image->rows[y].size;
	if (x < -w / 2) return 0;
	if (x >= w / 2) return 0;
	
	x += w / 2;
	
	int count = rotating_image->rows[y].count;
	
	int a = angle;
	a *= count;
	a += 128;
	a /= 256;
	a += x;
	a %= count;
	
	return rotating_image->rows[y].colors[a];
}

static void mimimi_rotating_image_stamp(struct mimimi_image *image, struct mimimi_rotating_image *rotating_image, unsigned char y_angle, unsigned char z_angle, int x0, int y0, int behind)
{
	int width = image->width;
	int height = image->height;
	
	x0 += rotating_image->oz * mimimi_cosine[y_angle] / 256;
	
	if (behind != 0) y_angle += 128;
	
	for (int x = 0 ; x < width ; x++)
	for (int y = 0 ; y < height ; y++)
	{
		if (behind != 0 && image->colors[x + y * width] != 0) continue;
		
		int x1 = x - x0;
		int y1 = y - y0;
		
		int x2 = x1 + y1 * mimimi_sine[z_angle] / 127;
		
		if (behind != 0) x2 = -x2 - 1;
		
		unsigned char color = mimimi_rotating_image_pixel(rotating_image, y_angle, x2, y1);
		if (color == 0) continue;
		
		image->colors[x + y * width] = color;
	}
}

static void mimimi_animation_layer(struct mimimi_image *image, struct mimimi_model_layer *layer, unsigned char y_angle, unsigned char z_angle, int x0, int y0)
{
	for (int i = 0 ; i < layer->count ; i++)
	{
		mimimi_rotating_image_stamp(image, layer->images[i], y_angle, z_angle, x0, y0, 0);
		mimimi_rotating_image_stamp(image, layer->images[i], y_angle, z_angle, x0, y0, 1);
	}
}

static void mimimi_animation_frame(struct mimimi_sprite_image *sprite_image, struct mimimi_model *model, unsigned char y_angle, unsigned char z_angle, unsigned char arm_angle)
{
	struct mimimi_image *image = &sprite_image->image;
	
	image->colors = sprite_image->colors;
	image->width = 26;
	image->height = 36;
	
	for (int x = 0 ; x < image->width ; x++)
	for (int y = 0 ; y < image->height ; y++)
		image->colors[x + y * image->width] = 0;
	
	int x = image->width / 2;
	
	int head_y = 13;
	int head_x = x + mimimi_sine[z_angle] * (image->height - head_y) / 127;
	
	int arm_y = 16;
	int arm_x = x + mimimi_sine[z_angle] * (image->height - arm_y) / 127;
	int left_arm_x = arm_x + 10 * mimimi_sine[y_angle] / 256;
	int right_arm_x = arm_x - 10 * mimimi_sine[y_angle] / 256;
	
	int leg_y = 24;
	int leg_x = x + mimimi_sine[z_angle] * (image->height - leg_y) / 127;
	int left_leg_x = leg_x + 6 * mimimi_sine[y_angle] / 256;
	int right_leg_x = leg_x - 6 * mimimi_sine[y_angle] / 256;
	
	int torso_y = 24;
	int torso_x = x + mimimi_sine[z_angle] * (image->height - torso_y) / 127;
	
	mimimi_animation_layer(image, model->left_leg, y_angle, -arm_angle, left_leg_x, leg_y);
	mimimi_animation_layer(image, model->right_leg, y_angle, arm_angle, right_leg_x, leg_y);
	if ((y_angle + 64) % 256 < 128)
	{
		mimimi_animation_layer(image, model->left_arm, y_angle, arm_angle, left_arm_x, arm_y);
		mimimi_animation_layer(image, model->torso, y_angle, z_angle, torso_x, torso_y);
		mimimi_animation_layer(image, model->right_arm, y_angle, -arm_angle, right_arm_x, arm_y);
	}
	else
	{
		mimimi_animation_layer(image, model->right_arm, y_angle, -arm_angle, right_arm_x, arm_y);
		mimimi_animation_layer(image, model->torso, y_angle, z_angle, torso_x, torso_y);
		mimimi_animation_layer(image, model->left_arm, y_angle, arm_angle, left_arm_x, arm_y);
	}
	mimimi_animation_layer(image, model->head, y_angle, 0, head_x, head_y);
}

static void mimimi_animation(struct mimimi_animation *animation, struct mimimi_model *model, unsigned char y_angle, unsigned char z_angle, unsigned char arm_angle)
{
	int count = mimimi_image_count;
	for (int i = 0 ; i < count ; i++)
		mimimi_animation_frame(animation->images + i, model, y_angle, z_angle, arm_angle * mimimi_sine[i * 255 / count] / 127);
}

static void mimimi_animation_set(struct mimimi_animation *animations, struct mimimi_model *model, int coefficient, unsigned char z_angle, unsigned char arm_angle)
{
	int count = mimimi_animation_count;
	for (int i = 0 ; i < count ; i++)
		mimimi_animation(animations + i, model, 64 + (12 + 34 * i / count) * coefficient, -z_angle * i / count * coefficient, arm_angle * i / count);
}

// adapted from https://rosettacode.org/wiki/Matrix_transposition#C
static void mimimi_transpose_image(struct mimimi_image *image)
{
	int width = image->width;
	int height = image->height;
	image->width = height;
	image->height = width;
	
	for (int start = 0 ; start < width * height ; start++)
	{
		int next = start;
		int i = 0;
		for (;;)
		{
			i++;
			next = (next % height) * width + next / height;
			if (next <= start) break;
		}
		
		if (i == 1) continue;
		if (next < start) continue;
		
		char tmp = image->colors[next = start];
		for (;;)
		{
			int i = (next % height) * width + next / height;
			image->colors[next] = (i == start) ? tmp : image->colors[i];
			next = i;
			if (next <= start) break;
		}
	}
}

static void mimimi_flip_image(struct mimimi_image *image)
{
	for (int x = 0 ; x < image->width / 2 ; x++)
	for (int y = 0 ; y < image->height ; y++)
	{
		unsigned char color = image->colors[image->width - x - 1 + y * image->width];
		image->colors[image->width - x - 1 + y * image->width] = image->colors[x + y * image->width];
		image->colors[x + y * image->width] = color;
	}
}

static void mimimi_rotate_image_cw(struct mimimi_image *image)
{
	mimimi_transpose_image(image);
	mimimi_flip_image(image);
}

static void mimimi_rotate_image_ccw(struct mimimi_image *image)
{
	mimimi_flip_image(image);
	mimimi_transpose_image(image);
}

struct mimimi_appearance *mimimi_appearance(struct mimimi_model *model, struct mimimi_allocator *allocator)
{
	struct mimimi_appearance *appearance = (*allocator->allocate)(sizeof *appearance);
	
	unsigned char z_angle = 9;
	unsigned char arm_angle = 16;
	unsigned char arm_flail_angle = 32;
	
	mimimi_animation_set(appearance->left.standing, model, 1, z_angle, arm_angle);
	mimimi_animation_set(appearance->right.standing, model, -1, z_angle, arm_angle);
	
	mimimi_animation(&appearance->left.knocked, model, 64 + 16, 0, arm_flail_angle);
	mimimi_animation(&appearance->right.knocked, model, 64 - 16, 0, arm_flail_angle);
	
	for (int i = 0 ; i < mimimi_image_count ; i++)
	{
		mimimi_rotate_image_cw(&appearance->left.knocked.images[i].image);
		mimimi_rotate_image_ccw(&appearance->right.knocked.images[i].image);
	}
	
	return appearance;
}

static struct mimimi_appearance mimimi_empty_appearance_value = {};
struct mimimi_appearance *mimimi_empty_appearance = &mimimi_empty_appearance_value;