Mirai's Miscellaneous Misadventures

M37 / core / animations.c

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

#include <mimimi/models.h>
#include <mimimi/appearances.h>
#include <mimimi/allocators.h>
#include <mimimi/animations.h>
#include <mimimi/assets.h>
#include <mimimi/poses.h>

#include "math.c"

static unsigned char mimimi_layer_pixel(struct mimimi_layer *layer, unsigned char y_angle, int x, int y)
{
	int h = layer->count;
	
	if (y < 0) return 0;
	if (y >= h) return 0;
	
	int w0 = layer->rows[y].width;
	int w1 = layer->rows[y].height;
	
	int s = 128 - mimimi_cosine[y_angle * 2 % 256];
	int w = s * w0 + (256 - s) * w1;
	w /= 512;
	
	if (x < -w) return 0;
	if (x >= w) return 0;
	
	int count = layer->rows[y].count;
	
	int a = y_angle + 64;
	a *= count;
	a += 128;
	a /= 256;
	a += x;
	a = mimimi_mod(a, count);
	
	return layer->rows[y].colors[a];
}

static void mimimi_swap(int *x, int *y)
{
	int z = *x;
	*x = *y;
	*y = z;
}

static void mimimi_layer(struct mimimi_image *image, struct mimimi_layer *layer, int x0, int y0, struct mimimi_pose_layer *pose_layer, int behind)
{
	int width = image->width;
	int height = image->height;
	
	unsigned char y_angle = pose_layer->y_angle;
	int z_angle = mimimi_mod(pose_layer->z_angle, 4);
	
	if (behind != 0) y_angle += 128;
	
	if (z_angle == 0)
	{
		for (int y = 0 ; y < height ; y++)
		for (int x = 0 ; x < width ; x++)
		{
			int x1 = x + layer->x - x0;
			int y1 = y + layer->y - y0;
			
			x1 += pose_layer->slant * (y - y0) / 256;
			
			if (behind != 0) x1 = -x1 - 1;
			
			unsigned char color = mimimi_layer_pixel(layer, y_angle, x1, y1);
			if (color != 0) image->colors[x + y * width] = color;
		}
	}
	
	if (z_angle == 1)
	{
		for (int y = 0 ; y < height ; y++)
		for (int x = 0 ; x < width ; x++)
		{
			int x1 = x - layer->y - x0;
			int y1 = y + layer->x - y0;
			
			y1 -= pose_layer->slant * (x - x0) / 256;
			
			if (behind != 0) y1 = -y1 - 1;
			
			unsigned char color = mimimi_layer_pixel(layer, y_angle, y1, -x1);
			if (color != 0) image->colors[x + y * width] = color;
		}
		
		return;
	}
	
	if (z_angle == 2)
	{
		for (int y = 0 ; y < height ; y++)
		for (int x = 0 ; x < width ; x++)
		{
			int x1 = x - layer->x - x0;
			int y1 = y - layer->y - y0;
			
			x1 += pose_layer->slant * (y - y0) / 256;
			
			if (behind != 0) x1 = -x1 + 1;
			
			unsigned char color = mimimi_layer_pixel(layer, y_angle, -x1, -y1);
			if (color != 0) image->colors[x + y * width] = color;
		}
		
		return;
	}
	
	if (z_angle == 3)
	{
		for (int y = 0 ; y < height ; y++)
		for (int x = 0 ; x < width ; x++)
		{
			int x1 = x + layer->y - x0;
			int y1 = y - layer->x - y0;
			
			y1 -= pose_layer->slant * (x - x0) / 256;
			
			if (behind != 0) y1 = -y1 + 1;
			
			unsigned char color = mimimi_layer_pixel(layer, y_angle, -y1, x1);
			if (color != 0) image->colors[x + y * width] = color;
		}
		
		return;
	}
}

static void mimimi_apply_y_angle(struct mimimi_model *model, struct mimimi_layer *layers, struct mimimi_pose_layer *pose_layers)
{
	for (int i = 0 ; i < model->count ; i++)
	{
		struct mimimi_layer *layer = layers + i;
		struct mimimi_pose_layer *pose_layer = pose_layers + i;
		int parent_index = model->layers[i].parent_index;
		
		if (i == parent_index) continue;
		
		int parent_y_angle = pose_layers[parent_index].y_angle;
		pose_layer->y_angle += parent_y_angle;
		
		unsigned char y_angle = parent_y_angle;
		
		int sin = mimimi_sine[y_angle];
		int cos = mimimi_cosine[y_angle];
		
		int x = layer->x;
		int z = layer->z;
		
		layer->x = cos*x / 128 + sin*z / 128;
		layer->z = cos*z / 128 - sin*x / 128;
	}
}

static void mimimi_apply_z_angle(struct mimimi_model *model, struct mimimi_layer *layers, struct mimimi_pose_layer *pose_layers, struct mimimi_allocator *allocator)
{
	int *xs = mimimi_allocate(allocator, sizeof *xs * model->count);
	int *ys = mimimi_allocate(allocator, sizeof *ys * model->count);
	
	for (int i = 0 ; i < model->count ; i++)
	{
		struct mimimi_layer *layer = layers + i;
		struct mimimi_pose_layer *pose_layer = pose_layers + i;
		int parent_index = model->layers[i].parent_index;
		
		xs[i] = layer->x;
		ys[i] = layer->y;
		if (i == parent_index) continue;
		xs[i] += xs[parent_index];
		ys[i] += ys[parent_index];
		
		int z_angle = mimimi_mod(pose_layer->z_angle, 4);
		
		int x = xs[i];
		int y = ys[i];
		if (z_angle == 1) x = -x, mimimi_swap(&x, &y);
		if (z_angle == 2) x = -x, y = -y;
		if (z_angle == 3) y = -y, mimimi_swap(&x, &y);
		layer->x = x - xs[parent_index];
		layer->y = y - ys[parent_index];
		
		if (z_angle == 1) xs[i] = -xs[i], mimimi_swap(xs + i, ys + i);
		if (z_angle == 2) xs[i] = -xs[i], ys[i] = -ys[i];
		if (z_angle == 3) ys[i] = -ys[i], mimimi_swap(xs + i, ys + i);
	}
	
	mimimi_deallocate(allocator, xs);
	mimimi_deallocate(allocator, ys);
}

static void mimimi_apply_slant(struct mimimi_model *model, struct mimimi_layer *layers, struct mimimi_pose_layer *pose_layers, struct mimimi_allocator *allocator)
{
	int *ys = mimimi_allocate(allocator, sizeof *ys * model->count);
	
	for (int i = 0 ; i < model->count ; i++)
	{
		struct mimimi_layer *layer = layers + i;
		struct mimimi_pose_layer *pose_layer = pose_layers + i;
		int parent_index = model->layers[i].parent_index;
		
		ys[i] = layer->y;
		if (i == parent_index) continue;
		ys[i] += ys[parent_index];
		
		layer->x += pose_layer->slant * ys[i] / 256;
	}
	
	mimimi_deallocate(allocator, ys);
}

static void mimimi_absolutize(struct mimimi_model *model, struct mimimi_layer *layers, struct mimimi_pose_layer *pose_layers)
{
	for (int i = 0 ; i < model->count ; i++)
	{
		struct mimimi_layer *layer = layers + i;
		struct mimimi_pose_layer *pose_layer = pose_layers + i;
		int parent_index = model->layers[i].parent_index;
		if (i == parent_index) continue;
		
		layer->x += layers[parent_index].x;
		layer->y += layers[parent_index].y;
		layer->z += layers[parent_index].z;
		layer->amplifier = layer->amplifier * layers[parent_index].amplifier / 32;
		
		pose_layer->slant += pose_layers[parent_index].slant;
		pose_layer->z_angle += pose_layers[parent_index].z_angle;
		pose_layer->z_angle = mimimi_mod(pose_layer->z_angle, 4);
	}
}

void mimimi_pose(struct mimimi_image *image, struct mimimi_model *model, struct mimimi_pose *pose, struct mimimi_allocator *allocator)
{
	struct mimimi_layer *layers = mimimi_allocate(allocator, sizeof *layers * model->count);
	struct mimimi_pose_layer *pose_layers = mimimi_allocate(allocator, sizeof *pose_layers * model->count);
	
	struct mimimi_half_layer { int behind; int z; struct mimimi_layer *layer; struct mimimi_pose_layer *pose_layer; };
	struct mimimi_half_layer *half_layers = mimimi_allocate(allocator, sizeof *half_layers * model->count * 2);
	
	for (int i = 0 ; i < model->count ; i++)
	{
		struct mimimi_layer *layer = layers + i;
		*layer = *model->layers[i].layer;
		
		struct mimimi_pose_layer *pose_layer = pose_layers + i;
		
		if (i < pose->count)
		{
			*pose_layer = pose->layers[i];
		}
		else
		{
			pose_layer->slant = 0;
			pose_layer->y_angle = 0;
			pose_layer->z_angle = 0;
		}
	}
	
	mimimi_apply_y_angle(model, layers, pose_layers);
	mimimi_apply_z_angle(model, layers, pose_layers, allocator);
	mimimi_apply_slant(model, layers, pose_layers, allocator);
	mimimi_absolutize(model, layers, pose_layers);
	
	for (int i = 0 ; i < model->count ; i++)
	{
		struct mimimi_layer *layer = layers + i;
		struct mimimi_pose_layer *pose_layer = pose_layers + i;
		
		int width = layer->width;
		
		int z = layer->z * layer->amplifier / 32;
		
		half_layers[i * 2 + 0].behind = 0;
		half_layers[i * 2 + 0].z = z + width;
		half_layers[i * 2 + 0].layer = layer;
		half_layers[i * 2 + 0].pose_layer = pose_layer;
		
		half_layers[i * 2 + 1].behind = 1;
		half_layers[i * 2 + 1].z = z - width;
		half_layers[i * 2 + 1].layer = layer;
		half_layers[i * 2 + 1].pose_layer = pose_layer;
	}
	
	// z ordering
	for (int i = 0 ; i < model->count * 2 ; i++)
	{
		int a = half_layers[i].z;
		
		int k = i;
		for (int j = i - 1 ; j >= 0 ; j--)
		{
			int b = half_layers[j].z;
			if (a >= b) break;
			
			struct mimimi_half_layer half_layer = half_layers[k];
			half_layers[k] = half_layers[j];
			half_layers[j] = half_layer;
			
			k = j;
		}
	}
	
	for (int y = 0 ; y < image->height ; y++)
	for (int x = 0 ; x < image->width ; x++)
		image->colors[x + y * image->width] = 0;
	
	for (int i = 0 ; i < model->count * 2 ; i++)
	{
		struct mimimi_half_layer *half_layer = half_layers + i;
		mimimi_layer(image, half_layer->layer, pose->x, pose->y, half_layer->pose_layer, half_layer->behind);
	}
	
	mimimi_deallocate(allocator, layers);
	mimimi_deallocate(allocator, pose_layers);
	mimimi_deallocate(allocator, half_layers);
}

static void mimimi_interpolate_pose_layer(struct mimimi_pose_layer *a, struct mimimi_pose_layer *b, int i, int count)
{
	int slant = a->slant * (count - i) / count;
	slant += mimimi_div(b->slant * i, count);
	
	int y_angle = a->y_angle * (count - i) / count;
	y_angle += mimimi_div(b->y_angle * i, count);
	
	int z_angle = a->z_angle * (count - i) / count;
	z_angle += mimimi_div(b->z_angle * i, count);
	
	a->slant = slant;
	a->y_angle = y_angle;
	a->z_angle = z_angle;
}

static void mimimi_interpolate_pose(struct mimimi_pose *a, struct mimimi_pose *b, int i, int count)
{
	if (count == 0) return;
	
	int x = a->x * (count - i) / count;
	x += mimimi_div(b->x * i, count);
	
	int y = a->y * (count - i) / count;
	y += mimimi_div(b->y * i, count);
	
	a->x = x;
	a->y = y;
	
	for (int j = 0 ; j < a->count ; j++)
		mimimi_interpolate_pose_layer(a->layers + j, b->layers + j, i, count);
}

void mimimi_movement(struct mimimi_animation *animation, struct mimimi_model *model, struct mimimi_movement *movement, struct mimimi_allocator *allocator)
{
	if (movement->count == 1)
	{
		for (int i = 0 ; i < animation->count ; i++)
			mimimi_pose(animation->images + i, model, movement->poses, allocator);
		return;
	}
	
	for (int i = 0 ; i < animation->count ; i++)
	{
		int j = i * (movement->count - 1) / animation->count;
		int k = i - j * animation->count / (movement->count - 1);
		
		struct mimimi_pose pose = movement->poses[j];
		pose.layers = mimimi_allocate(allocator, pose.count * sizeof *pose.layers);
		for (int i = 0 ; i < pose.count ; i++) pose.layers[i] = movement->poses[j].layers[i];
		
		mimimi_interpolate_pose(&pose, movement->poses + j + 1, k, animation->count / (movement->count - 1));
		mimimi_pose(animation->images + i, model, &pose, allocator);
		
		mimimi_deallocate(allocator, pose.layers);
	}
}

static void mimimi_appearance_animations(struct mimimi_animation_set *animations, struct mimimi_model *model, int x, int y, int coefficient, struct mimimi_allocator *allocator)
{
	int animation_count = animations->standing_animation_count;
	for (int i = 0 ; i < animation_count ; i++)
	{
		struct mimimi_pose_layer layers[3][7] = {0};
		
		int slant = -32 * i / animation_count * coefficient;
		int y_angle = 64 + (20 + 20 * i / animation_count) * coefficient;
		int arm_slant = 64 * i / animation_count;
		
		layers[0][0].y_angle = y_angle;
		layers[0][0].slant = +slant;
		layers[0][1].slant = -slant;
		layers[0][2].slant = -arm_slant;
		layers[0][3].slant = +arm_slant;
		layers[0][4].slant = +arm_slant;
		layers[0][5].slant = -arm_slant;
		
		layers[1][0].y_angle = y_angle;
		layers[1][0].slant = +slant;
		layers[1][1].slant = -slant;
		layers[1][2].slant = +arm_slant;
		layers[1][3].slant = -arm_slant;
		layers[1][4].slant = -arm_slant;
		layers[1][5].slant = +arm_slant;
		
		layers[2][0].y_angle = y_angle;
		layers[2][0].slant = +slant;
		layers[2][1].slant = -slant;
		layers[2][2].slant = -arm_slant;
		layers[2][3].slant = +arm_slant;
		layers[2][4].slant = +arm_slant;
		layers[2][5].slant = -arm_slant;
		
		struct mimimi_pose poses[] = {{x, y, 7, layers[0]}, {x, y, 7, layers[1]}, {x, y, 7, layers[2]}};
		struct mimimi_movement movement = {3, poses};
		mimimi_movement(animations->standing + i, model, &movement, allocator);
	}
	
	struct mimimi_pose_layer falling_layers[7] = {0};
	
	falling_layers[0].z_angle = coefficient;
	falling_layers[0].y_angle = 64 + 32 * coefficient;
	falling_layers[2].slant = -64 * coefficient;
	falling_layers[2].z_angle = coefficient;
	falling_layers[3].slant = 64 * coefficient;
	falling_layers[3].z_angle = coefficient;
	falling_layers[4].slant = 64 * coefficient;
	falling_layers[5].slant = 64 * coefficient;
	
	struct mimimi_pose falling_poses[] = {{x - 12 * coefficient, y - 12, 7, falling_layers}};
	struct mimimi_movement falling_movement = {1, falling_poses};
	mimimi_movement(animations->falling, model, &falling_movement, allocator);
	
	struct mimimi_pose_layer knocked_layers[1] = {0};
	
	knocked_layers[0].z_angle = -coefficient;
	knocked_layers[0].y_angle = 64 + 32 * coefficient;
	
	struct mimimi_pose knocked_poses[] = {{x - 12 * coefficient, y - 20, 1, knocked_layers}};
	struct mimimi_movement knocked_movement = {1, knocked_poses};
	mimimi_movement(animations->knocked, model, &knocked_movement, allocator);
	
	int image_count = animations->jumping->count;
	for (int i = 0 ; i < image_count ; i++)
	{
		struct mimimi_pose_layer layers[2] = {0};
		
		int slant = -32 * i / image_count * coefficient;
		int y_angle = 64 + (20 + 20 * i / image_count) * coefficient;
		
		layers[0].y_angle = y_angle;
		layers[0].slant = +slant;
		layers[1].slant = -slant;
		
		struct mimimi_pose pose = {x, y, 2, layers};
		mimimi_pose(animations->jumping->images + i, model, &pose, allocator);
	}
}

void mimimi_appearance(struct mimimi_appearance *appearance, struct mimimi_model *model, int x, int y, struct mimimi_allocator *allocator)
{
	mimimi_appearance_animations(&appearance->left, model, x, y, 1, allocator);
	mimimi_appearance_animations(&appearance->right, model, x, y, -1, allocator);
}