Mirai's Miscellaneous Misadventures

M21 / 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_rotating_image_pixel(struct mimimi_rotating_image *rotating_image, unsigned char slant, 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 = slant;
	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, int x0, int y0, struct mimimi_pose_layer *layer, int behind)
{
	int width = image->width;
	int height = image->height;
	
	unsigned char y_angle = layer->y_angle;
	unsigned char z_angle = layer->z_angle;
	unsigned char slant = layer->slant;
	z_angle %= 4;
	
	x0 += rotating_image->oz * mimimi_cosine[y_angle] / 256;
	
	if (behind != 0) y_angle += 128;
	
	for (int y = 0 ; y < height ; y++)
	for (int x = 0 ; x < width ; x++)
	{
		if (behind != 0 && image->colors[x + y * width] != 0) continue;
		
		int x1 = x - x0;
		int y1 = y - y0;
		
		if (z_angle == 1)
		{
			int x0 = x1;
			x1 = -y1;
			y1 = x0;
		}
		if (z_angle == 2)
		{
			x1 = -x1;
			y1 = -y1;
		}
		if (z_angle == 3)
		{
			int x0 = x1;
			x1 = y1;
			y1 = -x0;
		}
		
		int x2 = x1 + y1 * mimimi_sine[slant] / 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, int x0, int y0, struct mimimi_pose_layer *pose_layer, unsigned char z_angle)
{
	z_angle %= 4;
	
	if (z_angle == 1)
	{
		int x = x0;
		x0 = y0 + image->width/2 - image->height;
		y0 = image->width/2 + image->height - x;
	}
	if (z_angle == 2)
	{
		x0 = image->width - x0;
		y0 = image->height * 2 - y0;
	}
	if (z_angle == 3)
	{
		int x = x0;
		x0 = image->width/2 + image->height - y0;
		y0 = x - image->width/2 + image->height;
	}
	
	for (int i = 0 ; i < layer->count ; i++)
		mimimi_rotating_image_stamp(image, layer->images[i], x0, y0, pose_layer, 0),
		mimimi_rotating_image_stamp(image, layer->images[i], x0, y0, pose_layer, 1);
}

static void mimimi_join_pose_layer(struct mimimi_pose_layer *a, struct mimimi_pose_layer *b)
{
	a->slant += b->slant;
	a->y_angle += b->y_angle;
	a->z_angle += b->z_angle;
}

void mimimi_pose(struct mimimi_image *image, struct mimimi_model *model, struct mimimi_pose *pose)
{
	struct mimimi_pose_layer *torso = &pose->torso;
	struct mimimi_pose_layer head = pose->head;
	struct mimimi_pose_layer left_arm = pose->left_arm;
	struct mimimi_pose_layer right_arm = pose->right_arm;
	struct mimimi_pose_layer left_leg = pose->left_leg;
	struct mimimi_pose_layer right_leg = pose->right_leg;
	
	mimimi_join_pose_layer(&head, torso);
	mimimi_join_pose_layer(&left_arm, torso);
	mimimi_join_pose_layer(&right_arm, torso);
	mimimi_join_pose_layer(&left_leg, torso);
	mimimi_join_pose_layer(&right_leg, torso);
	
	for (int y = 0 ; y < image->height ; y++)
	for (int x = 0 ; x < image->width ; x++)
		image->colors[x + y * image->width] = 0;
	
	int x = pose->x + image->width / 2;
	int y = pose->y + image->height;
	
	unsigned char slant = torso->slant;
	unsigned char y_angle = torso->y_angle;
	
	int leg_y = y - 12;
	int leg_x = x + mimimi_sine[slant] * (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 = y - 12;
	int torso_x = x + mimimi_sine[slant] * (image->height - torso_y) / 127;
	
	int arm_y = y - 20;
	int arm_x = x + mimimi_sine[slant] * (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 head_y = y - 23;
	int head_x = x + mimimi_sine[slant] * (image->height - head_y) / 127;
	
	int z_angle = torso->z_angle;
	
	if ((torso->y_angle + 64) % 256 < 128)
	{
		mimimi_animation_layer(image, model->left_leg, left_leg_x, leg_y, &left_leg, z_angle);
		mimimi_animation_layer(image, model->right_leg, right_leg_x, leg_y, &right_leg, z_angle);
		
		mimimi_animation_layer(image, model->left_arm, left_arm_x, arm_y, &left_arm, z_angle);
		mimimi_animation_layer(image, model->torso, torso_x, torso_y, torso, z_angle);
		mimimi_animation_layer(image, model->right_arm, right_arm_x, arm_y, &right_arm, z_angle);
	}
	else
	{
		mimimi_animation_layer(image, model->right_leg, right_leg_x, leg_y, &right_leg, z_angle);
		mimimi_animation_layer(image, model->left_leg, left_leg_x, leg_y, &left_leg, z_angle);
		
		mimimi_animation_layer(image, model->right_arm, right_arm_x, arm_y, &right_arm, z_angle);
		mimimi_animation_layer(image, model->torso, torso_x, torso_y, torso, z_angle);
		mimimi_animation_layer(image, model->left_arm, left_arm_x, arm_y, &left_arm, z_angle);
	}
	mimimi_animation_layer(image, model->head, head_x, head_y, &head, z_angle);
}

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;
	
	mimimi_interpolate_pose_layer(&a->torso, &b->torso, i, count);
	mimimi_interpolate_pose_layer(&a->head, &b->head, i, count);
	mimimi_interpolate_pose_layer(&a->left_arm, &b->left_arm, i, count);
	mimimi_interpolate_pose_layer(&a->right_arm, &b->right_arm, i, count);
	mimimi_interpolate_pose_layer(&a->left_leg, &b->left_leg, i, count);
	mimimi_interpolate_pose_layer(&a->right_leg, &b->right_leg, i, count);
}

void mimimi_movement(struct mimimi_animation *animation, struct mimimi_model *model, struct mimimi_movement *movement)
{
	if (movement->pose_count == 1)
	{
		for (int i = 0 ; i < animation->image_count ; i++)
			mimimi_pose(animation->images + i, model, movement->poses);
		return;
	}
	
	for (int i = 0 ; i < animation->image_count ; i++)
	{
		int j = i * (movement->pose_count - 1) / animation->image_count;
		int k = i - j * animation->image_count / (movement->pose_count - 1);
		
		struct mimimi_pose pose = movement->poses[j];
		mimimi_interpolate_pose(&pose, movement->poses + j + 1, k, animation->image_count / (movement->pose_count - 1));
		mimimi_pose(animation->images + i, model, &pose);
	}
}

static void mimimi_appearance_animations(struct mimimi_animation_set *animations, struct mimimi_model *model, int coefficient)
{
	int count = animations->standing_animation_count;
	for (int i = 0 ; i < count ; i++)
	{
		struct mimimi_pose poses[3] = {};
		struct mimimi_movement movement = {3, poses};
		
		unsigned char slant = -8 * i / count * coefficient;
		unsigned char y_angle = 64 + (12 + 24 * i / count) * coefficient;
		unsigned char arm_slant = 16 * i / count;
		
		poses[0].torso.slant = slant;
		poses[0].torso.y_angle = y_angle;
		poses[0].left_arm.slant = -arm_slant;
		poses[0].right_arm.slant = arm_slant;
		poses[0].left_leg.slant = arm_slant;
		poses[0].right_leg.slant = -arm_slant;
		poses[0].head.slant = -slant;
		
		poses[1].torso.slant = slant;
		poses[1].torso.y_angle = y_angle;
		poses[1].left_arm.slant = arm_slant;
		poses[1].right_arm.slant = -arm_slant;
		poses[1].left_leg.slant = -arm_slant;
		poses[1].right_leg.slant = arm_slant;
		poses[1].head.slant = -slant;
		
		poses[2] = poses[0];
		
		mimimi_movement(animations->standing + i, model, &movement);
		
		struct mimimi_pose falling_poses[1] = {};
		struct mimimi_movement falling_movement = {1, falling_poses};
		
		falling_poses[0].torso.z_angle = -coefficient;
		falling_poses[0].torso.y_angle = 64 + 32 * coefficient;
		falling_poses[0].left_arm.z_angle = -coefficient;
		falling_poses[0].right_arm.z_angle = -coefficient;
		falling_poses[0].left_leg.slant = -12 * coefficient;
		falling_poses[0].right_leg.slant = -12 * coefficient;
		falling_poses[0].left_arm.slant = 12 * coefficient;
		falling_poses[0].right_arm.slant = -12 * coefficient;
		falling_poses[0].x = -10 * coefficient;
		falling_poses[0].y = 16;
		
		mimimi_movement(animations->falling, model, &falling_movement);
		
		struct mimimi_pose knocked_poses[1] = {};
		struct mimimi_movement knocked_movement = {1, knocked_poses};
		
		knocked_poses[0].torso.z_angle = -coefficient;
		knocked_poses[0].torso.y_angle = 64 + 32 * coefficient;
		knocked_poses[0].x = -20 * coefficient;
		knocked_poses[0].y = 16;
		
		mimimi_movement(animations->knocked, model, &knocked_movement);
	}
}

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