use crate::core::{Matrix44, draw_order_from_raw};
use crate::moc3::{Moc3DrawableBlendMode, Moc3DrawableMesh, Moc3DrawableVertex};
#[derive(Debug, Clone, PartialEq)]
pub struct DrawableInfo {
texture_index: i32,
blend_mode: Moc3DrawableBlendMode,
opacity: f32,
draw_order: f32,
render_order: i32,
masks: Vec<i32>,
inverted_mask: bool,
bounds: Option<ClippingRect>,
}
impl DrawableInfo {
pub fn from_mesh(mesh: &Moc3DrawableMesh) -> Self {
Self {
texture_index: mesh.texture_index(),
blend_mode: mesh.blend_mode(),
opacity: mesh.opacity(),
draw_order: mesh.draw_order(),
render_order: mesh.render_order(),
masks: mesh.masks().to_vec(),
inverted_mask: mesh.is_inverted_mask(),
bounds: drawable_vertex_bounds(mesh.vertices()),
}
}
pub fn texture_index(&self) -> i32 {
self.texture_index
}
pub fn blend_mode(&self) -> Moc3DrawableBlendMode {
self.blend_mode
}
pub fn opacity(&self) -> f32 {
self.opacity
}
pub fn draw_order(&self) -> f32 {
self.draw_order
}
pub fn render_order(&self) -> i32 {
self.render_order
}
pub fn masks(&self) -> &[i32] {
&self.masks
}
pub fn inverted_mask(&self) -> bool {
self.inverted_mask
}
pub fn bounds(&self) -> Option<ClippingRect> {
self.bounds
}
}
pub fn draw_order_indices(drawables: &[DrawableInfo]) -> Vec<usize> {
let mut indices = (0..drawables.len()).collect::<Vec<_>>();
if render_orders_are_total_rank(drawables) {
indices.sort_by_key(|&index| drawables[index].render_order);
return indices;
}
indices.sort_by(|left, right| {
draw_order_from_raw(drawables[*left].draw_order)
.cmp(&draw_order_from_raw(drawables[*right].draw_order))
.then_with(|| {
drawables[*left]
.render_order
.cmp(&drawables[*right].render_order)
})
.then_with(|| left.cmp(right))
});
indices
}
fn render_orders_are_total_rank(drawables: &[DrawableInfo]) -> bool {
let count = drawables.len();
if count == 0 {
return false;
}
let mut seen = vec![false; count];
let mut identity = true;
for (index, drawable) in drawables.iter().enumerate() {
let Ok(rank) = usize::try_from(drawable.render_order) else {
return false;
};
match seen.get_mut(rank) {
Some(slot) if !*slot => *slot = true,
_ => return false,
}
identity &= rank == index;
}
!identity
}
fn drawable_vertex_bounds(vertices: &[Moc3DrawableVertex]) -> Option<ClippingRect> {
let first = vertices.first()?;
let mut min_x = first.position()[0];
let mut min_y = first.position()[1];
let mut max_x = min_x;
let mut max_y = min_y;
for vertex in vertices.iter().skip(1) {
let [x, y] = vertex.position();
min_x = min_x.min(x);
min_y = min_y.min(y);
max_x = max_x.max(x);
max_y = max_y.max(y);
}
Some(ClippingRect::new(
min_x,
min_y,
max_x - min_x,
max_y - min_y,
))
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MaskChannel {
Red,
Green,
Blue,
Alpha,
}
impl MaskChannel {
pub fn index(self) -> usize {
match self {
Self::Red => 0,
Self::Green => 1,
Self::Blue => 2,
Self::Alpha => 3,
}
}
pub fn flag(self) -> [f32; 4] {
match self {
Self::Red => [1.0, 0.0, 0.0, 0.0],
Self::Green => [0.0, 1.0, 0.0, 0.0],
Self::Blue => [0.0, 0.0, 1.0, 0.0],
Self::Alpha => [0.0, 0.0, 0.0, 1.0],
}
}
fn from_index(index: usize) -> Option<Self> {
match index {
0 => Some(Self::Red),
1 => Some(Self::Green),
2 => Some(Self::Blue),
3 => Some(Self::Alpha),
_ => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ClippingRect {
x: f32,
y: f32,
width: f32,
height: f32,
}
impl ClippingRect {
pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn x(&self) -> f32 {
self.x
}
pub fn y(&self) -> f32 {
self.y
}
pub fn width(&self) -> f32 {
self.width
}
pub fn height(&self) -> f32 {
self.height
}
fn expanded(self, margin_ratio: f32) -> Self {
let margin_x = self.width * margin_ratio;
let margin_y = self.height * margin_ratio;
Self::new(
self.x - margin_x,
self.y - margin_y,
self.width + margin_x * 2.0,
self.height + margin_y * 2.0,
)
}
fn union(self, other: Self) -> Self {
let min_x = self.x.min(other.x);
let min_y = self.y.min(other.y);
let max_x = (self.x + self.width).max(other.x + other.width);
let max_y = (self.y + self.height).max(other.y + other.height);
Self::new(min_x, min_y, max_x - min_x, max_y - min_y)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct ClippingLayout {
channel: MaskChannel,
bounds: ClippingRect,
}
impl ClippingLayout {
pub fn new(channel: MaskChannel, bounds: ClippingRect) -> Self {
Self { channel, bounds }
}
pub fn channel(&self) -> MaskChannel {
self.channel
}
pub fn channel_flag(&self) -> [f32; 4] {
self.channel.flag()
}
pub fn bounds(&self) -> ClippingRect {
self.bounds
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClippingLayoutError {
TooManyMasksForSingleTexture { mask_count: usize },
MissingDrawableBounds { drawable_index: usize },
MissingLayout { context_index: usize },
MissingMaskMatrix { context_index: usize },
MissingDrawMatrix { context_index: usize },
InvalidMaskDrawableIndex { drawable_index: i32 },
DegenerateClippedBounds { context_index: usize },
}
impl std::fmt::Display for ClippingLayoutError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TooManyMasksForSingleTexture { mask_count } => write!(
formatter,
"single mask texture supports at most 36 clipping contexts, got {mask_count}"
),
Self::MissingDrawableBounds { drawable_index } => {
write!(
formatter,
"drawable {drawable_index} has no clipping bounds"
)
}
Self::MissingLayout { context_index } => {
write!(formatter, "clipping context {context_index} has no layout")
}
Self::MissingMaskMatrix { context_index } => write!(
formatter,
"clipping context {context_index} has no mask matrix"
),
Self::MissingDrawMatrix { context_index } => write!(
formatter,
"clipping context {context_index} has no draw matrix"
),
Self::InvalidMaskDrawableIndex { drawable_index } => {
write!(formatter, "invalid mask drawable index {drawable_index}")
}
Self::DegenerateClippedBounds { context_index } => write!(
formatter,
"clipping context {context_index} has degenerate clipped bounds"
),
}
}
}
impl std::error::Error for ClippingLayoutError {}
#[derive(Debug, Clone, PartialEq)]
pub struct ClippingContext {
masks: Vec<i32>,
inverted: bool,
drawable_indices: Vec<usize>,
layout: Option<ClippingLayout>,
all_clipped_draw_rect: Option<ClippingRect>,
matrix_for_mask: Option<Matrix44>,
matrix_for_draw: Option<Matrix44>,
}
impl ClippingContext {
pub fn masks(&self) -> &[i32] {
&self.masks
}
pub fn inverted(&self) -> bool {
self.inverted
}
pub fn drawable_indices(&self) -> &[usize] {
&self.drawable_indices
}
pub fn layout(&self) -> Option<ClippingLayout> {
self.layout
}
pub fn all_clipped_draw_rect(&self) -> Option<ClippingRect> {
self.all_clipped_draw_rect
}
pub fn matrix_for_mask(&self) -> Option<Matrix44> {
self.matrix_for_mask
}
pub fn matrix_for_draw(&self) -> Option<Matrix44> {
self.matrix_for_draw
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ClippingPlan {
contexts: Vec<ClippingContext>,
unmasked_drawable_indices: Vec<usize>,
}
impl ClippingPlan {
pub fn from_drawables(drawables: &[DrawableInfo]) -> Self {
let mut contexts = Vec::<ClippingContext>::new();
let mut unmasked_drawable_indices = Vec::new();
for (drawable_index, drawable) in drawables.iter().enumerate() {
if drawable.masks().is_empty() {
unmasked_drawable_indices.push(drawable_index);
continue;
}
if let Some(context) = contexts.iter_mut().find(|context| {
context.inverted == drawable.inverted_mask()
&& same_mask_set(&context.masks, drawable.masks())
}) {
context.drawable_indices.push(drawable_index);
} else {
contexts.push(ClippingContext {
masks: drawable.masks().to_vec(),
inverted: drawable.inverted_mask(),
drawable_indices: vec![drawable_index],
layout: None,
all_clipped_draw_rect: None,
matrix_for_mask: None,
matrix_for_draw: None,
});
}
}
Self {
contexts,
unmasked_drawable_indices,
}
}
pub fn contexts(&self) -> &[ClippingContext] {
&self.contexts
}
pub fn unmasked_drawable_indices(&self) -> &[usize] {
&self.unmasked_drawable_indices
}
pub fn assign_single_texture_layouts(&mut self) -> Result<(), ClippingLayoutError> {
let using_clip_count = self.contexts.len();
if using_clip_count > 36 {
return Err(ClippingLayoutError::TooManyMasksForSingleTexture {
mask_count: using_clip_count,
});
}
let div = using_clip_count / 4;
let rem = using_clip_count % 4;
let mut context_index = 0;
for channel_index in 0..4 {
let layout_count = div + usize::from(channel_index < rem);
let channel = MaskChannel::from_index(channel_index).expect("valid RGBA channel");
for layout_index in 0..layout_count {
self.contexts[context_index].layout = Some(ClippingLayout::new(
channel,
clipping_layout_bounds(layout_index, layout_count),
));
context_index += 1;
}
}
Ok(())
}
pub fn prepare_single_texture_masks(
&mut self,
drawables: &[DrawableInfo],
) -> Result<(), ClippingLayoutError> {
self.assign_single_texture_layouts()?;
for context_index in 0..self.contexts.len() {
let layout = self.contexts[context_index]
.layout
.ok_or(ClippingLayoutError::MissingLayout { context_index })?;
let bounds = clipped_draw_total_bounds(
drawables,
self.contexts[context_index].drawable_indices(),
)?
.ok_or(ClippingLayoutError::DegenerateClippedBounds { context_index })?
.expanded(0.05);
let (matrix_for_mask, matrix_for_draw) = clipping_matrices(bounds, layout.bounds())
.ok_or(ClippingLayoutError::DegenerateClippedBounds { context_index })?;
self.contexts[context_index].all_clipped_draw_rect = Some(bounds);
self.contexts[context_index].matrix_for_mask = Some(matrix_for_mask);
self.contexts[context_index].matrix_for_draw = Some(matrix_for_draw);
}
Ok(())
}
}
fn same_mask_set(left: &[i32], right: &[i32]) -> bool {
if left.len() != right.len() {
return false;
}
let mut sorted_left = left.to_vec();
let mut sorted_right = right.to_vec();
sorted_left.sort_unstable();
sorted_right.sort_unstable();
sorted_left == sorted_right
}
fn clipping_layout_bounds(layout_index: usize, layout_count: usize) -> ClippingRect {
match layout_count {
0 => ClippingRect::new(0.0, 0.0, 0.0, 0.0),
1 => ClippingRect::new(0.0, 0.0, 1.0, 1.0),
2 => ClippingRect::new(layout_index as f32 * 0.5, 0.0, 0.5, 1.0),
3 | 4 => {
let xpos = layout_index % 2;
let ypos = layout_index / 2;
ClippingRect::new(xpos as f32 * 0.5, ypos as f32 * 0.5, 0.5, 0.5)
}
5..=9 => {
let xpos = layout_index % 3;
let ypos = layout_index / 3;
ClippingRect::new(xpos as f32 / 3.0, ypos as f32 / 3.0, 1.0 / 3.0, 1.0 / 3.0)
}
_ => unreachable!("single texture channel layouts are capped at 9 cells"),
}
}
fn clipped_draw_total_bounds(
drawables: &[DrawableInfo],
drawable_indices: &[usize],
) -> Result<Option<ClippingRect>, ClippingLayoutError> {
let mut bounds: Option<ClippingRect> = None;
for &drawable_index in drawable_indices {
let drawable_bounds = drawables
.get(drawable_index)
.ok_or(ClippingLayoutError::MissingDrawableBounds { drawable_index })?
.bounds()
.ok_or(ClippingLayoutError::MissingDrawableBounds { drawable_index })?;
bounds = Some(match bounds {
Some(bounds) => bounds.union(drawable_bounds),
None => drawable_bounds,
});
}
Ok(bounds)
}
fn clipping_matrices(bounds: ClippingRect, layout: ClippingRect) -> Option<(Matrix44, Matrix44)> {
if bounds.width <= 0.0 || bounds.height <= 0.0 {
return None;
}
let scale_x = layout.width / bounds.width;
let scale_y = layout.height / bounds.height;
let draw_translate_x = -bounds.x * scale_x + layout.x;
let normalized_translate_y = -bounds.y * scale_y + layout.y;
let texture_translate_y = 1.0 - layout.y + bounds.y * scale_y;
let mut matrix_for_draw = Matrix44::identity();
matrix_for_draw.scale(scale_x, -scale_y);
matrix_for_draw.translate(draw_translate_x, texture_translate_y);
let mut matrix_for_mask = Matrix44::identity();
matrix_for_mask.scale(scale_x * 2.0, scale_y * 2.0);
matrix_for_mask.translate(
draw_translate_x * 2.0 - 1.0,
normalized_translate_y * 2.0 - 1.0,
);
Some((matrix_for_mask, matrix_for_draw))
}